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

org.sonarsource.sonarlint.ls.commands.ShowAllLocationsCommand Maven / Gradle / Ivy

There is a newer version: 3.12.0.75621
Show newest version
/*
 * SonarLint Language Server
 * Copyright (C) 2009-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.ls.commands;

import java.net.URI;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityDto;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TextRangeWithHashDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueFlowDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueLocationDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;
import org.sonarsource.sonarlint.core.rpc.protocol.common.FlowDto;
import org.sonarsource.sonarlint.core.rpc.protocol.common.LocationDto;
import org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;
import org.sonarsource.sonarlint.ls.LocalCodeFile;
import org.sonarsource.sonarlint.ls.connected.DelegatingFinding;
import org.sonarsource.sonarlint.ls.domain.TaintIssue;
import org.sonarsource.sonarlint.ls.util.Utils;

import static org.sonarsource.sonarlint.ls.util.TextRangeUtils.textRangeWithHashDtoToTextRangeDto;
import static org.sonarsource.sonarlint.ls.util.URIUtils.getFullFileUriFromFragments;

public final class ShowAllLocationsCommand {

  public static final String ID = "SonarLint.ShowAllLocations";

  private ShowAllLocationsCommand() {
    // NOP
  }

  public static class Param {
    private final URI fileUri;
    private final String message;
    private final boolean shouldOpenRuleDescription;
    private final String severity;
    private final String ruleKey;
    private final List flows;
    private final String connectionId;
    private final String creationDate;
    private final TextRangeDto textRange;
    private boolean codeMatches = false;


    private Param(DelegatingFinding issue) {
      this.fileUri = issue.getFileUri();
      this.message = issue.getMessage();
      var issueSeverity = issue.getSeverity();
      this.severity = issueSeverity != null ? issueSeverity.toString() : "";
      this.ruleKey = issue.getRuleKey();
      this.flows = issue.flows().stream().map(Flow::new).toList();
      this.textRange = issue.getTextRange();
      this.connectionId = null;
      this.creationDate = null;
      this.shouldOpenRuleDescription = true;
    }

    public Param(ShowIssueParams showIssueParams, @Nullable String connectionId, boolean shouldOpenRuleDescription) {
      this.fileUri = getFullFileUriFromFragments(showIssueParams.getConfigurationScopeId(), showIssueParams.getIssueDetails().getIdeFilePath());
      this.message = showIssueParams.getIssueDetails().getMessage();
      this.shouldOpenRuleDescription = shouldOpenRuleDescription;
      this.severity = "";
      this.ruleKey = showIssueParams.getIssueDetails().getRuleKey();
      this.flows = showIssueParams.getIssueDetails().getFlows().stream().map(f -> new Flow(f, showIssueParams.getConfigurationScopeId())).toList();
      this.textRange = new TextRangeDto(showIssueParams.getIssueDetails().getTextRange().getStartLine(),
        showIssueParams.getIssueDetails().getTextRange().getStartLineOffset(),
        showIssueParams.getIssueDetails().getTextRange().getEndLine(),
        showIssueParams.getIssueDetails().getTextRange().getEndLineOffset());
      this.connectionId = connectionId;
      this.creationDate = showIssueParams.getIssueDetails().getCreationDate();
      try {
        String localCode;
        if (this.textRange.getStartLine() == 0 || this.textRange.getEndLine() == 0) {
          // this is a file-level issue
          localCode = LocalCodeFile.from(this.fileUri).content();
        } else {
          localCode = LocalCodeFile.from(this.fileUri).codeAt(this.textRange);
        }
        if (localCode == null) {
          this.codeMatches = false;
        } else {
          this.codeMatches = showIssueParams.getIssueDetails().getCodeSnippet().equals(localCode);
        }
      } catch (Exception e) {
        // not a valid range
        this.codeMatches = false;
      }
    }

    Param(TaintIssue taint, String connectionId, Map localFileCache) {
      this.fileUri = getFullFileUriFromFragments(taint.getWorkspaceFolderUri(), taint.getIdeFilePath());
      this.message = taint.getMessage();
      this.severity = taint.getSeverityMode().isLeft() ? taint.getSeverityMode().getLeft().getSeverity().toString() : "";
      this.ruleKey = taint.getRuleKey();
      this.flows = taint.getFlows().stream().map(f -> new Flow(f, localFileCache, taint.getWorkspaceFolderUri())).toList();
      this.textRange = textRangeWithHashDtoToTextRangeDto(taint.getTextRange());
      this.connectionId = connectionId;
      this.creationDate = DateTimeFormatter.ISO_DATE_TIME.format(taint.getIntroductionDate().atOffset(ZoneOffset.UTC));
      this.shouldOpenRuleDescription = true;
    }

    public URI getFileUri() {
      return fileUri;
    }

    public String getMessage() {
      return message;
    }

    public String getSeverity() {
      return severity;
    }

    public String getRuleKey() {
      return ruleKey;
    }

    @CheckForNull
    public String getConnectionId() {
      return connectionId;
    }

    @CheckForNull
    public String getCreationDate() {
      return creationDate;
    }

    public List getFlows() {
      return flows;
    }

    public TextRangeDto getTextRange() {
      return textRange;
    }

    public boolean getCodeMatches() {
      return codeMatches;
    }

    public boolean isShouldOpenRuleDescription() {
      return shouldOpenRuleDescription;
    }

  }

  static class Flow {
    private final List locations;

    private Flow(IssueFlowDto flow) {
      this.locations = flow.getLocations().stream().map(Location::new).toList();
    }

    private Flow(FlowDto flow, String workspaceFolderUri) {
      this.locations = flow.getLocations().stream().map(locationDto -> new Location(locationDto, new HashMap<>(), workspaceFolderUri)).toList();
    }

    private Flow(TaintVulnerabilityDto.FlowDto flow, Map localFileCache, String workspaceFolderUri) {
      this.locations = flow.getLocations().stream().map(l -> new Location(l, localFileCache, workspaceFolderUri)).toList();
    }

    public List getLocations() {
      return locations;
    }
  }

  static class Location {
    private final TextRangeWithHashDto textRange;
    private URI uri;
    private final String filePath;
    private final String message;
    private boolean exists = false;
    private boolean codeMatches = false;

    private Location(IssueLocationDto location) {
      var locationTextRange = location.getTextRange();
      this.textRange = locationTextRange != null ? new TextRangeWithHashDto(locationTextRange.getStartLine(),
        locationTextRange.getStartLineOffset(),
        locationTextRange.getEndLine(),
        locationTextRange.getEndLineOffset(), "") : null;
      this.uri = location.getFileUri();
      this.filePath = this.uri == null ? null : this.uri.getPath();
      this.message = location.getMessage();
      this.exists = true;
      this.codeMatches = true;
    }

    private Location(LocationDto location, Map localCodeCache, String workspaceFolderUri) {
      this.textRange = new TextRangeWithHashDto(location.getTextRange().getStartLine(),
        location.getTextRange().getStartLineOffset(),
        location.getTextRange().getEndLine(),
        location.getTextRange().getEndLineOffset(), Utils.hash(location.getCodeSnippet()));
      this.uri = getFullFileUriFromFragments(workspaceFolderUri, location.getIdeFilePath());
      this.message = location.getMessage();
      this.filePath = location.getIdeFilePath().toUri().toString();
      String localCode = codeExists(localCodeCache);
      if (localCode != null) {
        this.exists = true;
        var locationTextRange = location.getTextRange();
        if (locationTextRange == null) {
          this.codeMatches = false;
        } else {
          this.codeMatches = location.getCodeSnippet().equals(localCode);
        }
      }
    }

    private String codeExists(Map localCodeCache) {
      if (this.uri == null) {
        this.exists = false;
      } else {
        String localCode = localCodeCache.computeIfAbsent(this.uri, LocalCodeFile::from).codeAt(this.textRange);
        if (localCode == null) {
          this.exists = false;
        } else {
          return localCode;
        }
      }
      return null;
    }

    private Location(TaintVulnerabilityDto.FlowDto.LocationDto location, Map localCodeCache, String workspaceFolderUri) {
      this.textRange = location.getTextRange();
      var locationFilePath = location.getFilePath();
      if (locationFilePath != null) {
        this.uri = getFullFileUriFromFragments(workspaceFolderUri, locationFilePath);
        this.filePath = locationFilePath.toString();
      } else {
        this.filePath = "Could not locate file";
      }
      this.message = location.getMessage();
      String localCode = codeExists(localCodeCache);
      if (localCode != null) {
        this.exists = true;
        var locationTextRange = location.getTextRange();
        if (locationTextRange == null) {
          this.codeMatches = false;
        } else {
          var textRangeHash = locationTextRange.getHash();
          var localCodeHash = Utils.hash(localCode);
          this.codeMatches = textRangeHash.equals(localCodeHash);
        }
      }
    }

    @CheckForNull
    public TextRangeWithHashDto getTextRange() {
      return textRange;
    }

    public URI getUri() {
      return uri;
    }

    public String getFilePath() {
      return filePath;
    }

    public String getMessage() {
      return message;
    }

    public boolean getExists() {
      return exists;
    }

    public boolean isCodeMatches() {
      return codeMatches;
    }
  }

  public static Param params(DelegatingFinding issue) {
    return new Param(issue);
  }

  public static Param params(TaintIssue issue, String connectionId) {
    return new Param(issue, connectionId, new HashMap<>());
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy