Skip to content

Commit

Permalink
Local change processing for sonarcloud issues
Browse files Browse the repository at this point in the history
  • Loading branch information
philippefichet committed Dec 23, 2023
1 parent 7c91a96 commit b81a978
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package com.github.philippefichet.sonarlint4netbeans;

import com.github.philippefichet.sonarlint4netbeans.remote.SourceLineHashesComputer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -27,6 +28,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.openide.util.Exceptions;
import org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;

/**
Expand All @@ -41,6 +43,7 @@ public class FSClientInputFile implements ClientInputFile {
private final Charset encoding;
private final String content;
private final List<ClientInputFileListener> clientInputFileURIEvents = new ArrayList<>();
private SourceLineHashesComputer sourceLineHashesComputer;

public FSClientInputFile(String content, Path path, String relativePath, boolean isTest, Charset encoding) {
this.content = content;
Expand Down Expand Up @@ -105,6 +108,21 @@ private void consumePathURI() {
}
}

public List<String> getLineHashes() {
if (sourceLineHashesComputer == null) {
try {
String[] split = contents().split("\n");
sourceLineHashesComputer = new SourceLineHashesComputer(split.length);
for (String line : split) {
sourceLineHashesComputer.addLine(line);
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return sourceLineHashesComputer.getLineHashes();
}

@Override
public String toString() {
return "FSClientInputFile{" + "path=" + path + ", relativePath=" + relativePath + ", isTest=" + isTest + ", encoding=" + encoding + '}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
import org.sonarsource.nodejs.NodeCommandBuilderImpl;
import org.sonarsource.nodejs.NodeCommandException;
import org.sonarsource.sonarlint.core.analysis.api.AnalysisResults;
import org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;
import org.sonarsource.sonarlint.core.client.api.common.RuleDetails;
import org.sonarsource.sonarlint.core.client.api.common.analysis.Issue;
import org.sonarsource.sonarlint.core.client.api.common.analysis.IssueListener;
Expand Down Expand Up @@ -268,74 +267,20 @@ public static List<Issue> analyze(FileObject fileObject, String contentToAnalyze
Path path = project == null
? toFile.getParentFile().toPath()
: FileUtil.toFile(project.getProjectDirectory()).toPath();
List<ClientInputFile> clientInputFiles = new ArrayList<>();
boolean applyTestRules = useTestRules && dataManager.isTest(toFile);
String relativizeToFile = path.relativize(toFile.toPath()).toString();
clientInputFiles.add(new FSClientInputFile(
FSClientInputFile clientInputFile = new FSClientInputFile(
contentToAnalyze == null ? new String(Files.readAllBytes(toFile.toPath())) : contentToAnalyze,
toFile.toPath().toAbsolutePath(),
relativizeToFile,
applyTestRules,
FileEncodingQuery.getEncoding(fileObject))
FileEncodingQuery.getEncoding(fileObject)
);

SonarLintProjectPreferenceScope preferencesScope = dataManager.getPreferencesScope(project);
String sonarLintHome = System.getProperty("user.home") + File.separator + ".sonarlint4netbeans";
if (preferencesScope == SonarLintProjectPreferenceScope.REMOTE) {
SonarLintRemoteEngine remoteEngine = Lookup.getDefault().lookup(SonarLintRemoteEngine.class);
SonarLintRemoteProjectConfiguration sonarLintRemoteProjectConfiguration = SonarLintRemoteProjectConfiguration.fromProject(project);
ConnectedAnalysisConfiguration build = ConnectedAnalysisConfiguration.builder()
.setProjectKey(sonarLintRemoteProjectConfiguration.getProjectKey())
.addInputFiles(clientInputFiles)
.setBaseDir(new File(sonarLintHome).toPath())
// TODO Remove dependency with sonarLintEngine
.putAllExtraProperties(getMergedExtraPropertiesAndReplaceVariables(sonarLintEngine, project))
.build();
List<Issue> listener = new ArrayList<>();
AnalysisResults remoteAnalyze = remoteEngine.analyze(
sonarLintRemoteProjectConfiguration,
build,
listener::add,
(String formattedMessage, ClientLogOutput.Level level) -> {
System.out.println(level + " | " + formattedMessage);
},
new ClientProgressMonitor() {
@Override
public boolean isCanceled() {
return false;
}

@Override
public void setMessage(String msg) {
LOG.info("ClientProgressMonitor.setMessage(\"" + msg + "\")");
}

@Override
public void setFraction(float fraction) {
LOG.info("ClientProgressMonitor.setFraction(" + fraction + ")");
}

@Override
public void setIndeterminate(boolean indeterminate) {
LOG.info("ClientProgressMonitor.setIndeterminate(" + indeterminate + ")");
}
}
);

List<ServerIssue> serverIssues = remoteEngine.getServerIssues(sonarLintRemoteProjectConfiguration, relativizeToFile);
serverIssues.stream()
.map(
s ->
new SonarLintIssueWrapperForServerIssue(
clientInputFiles.get(0),
s,
sonarLintEngine.getRuleDetails(s.getRuleKey())
)
)
.forEach(listener::add);
LOG.fine(() -> "Analyze (Remote) result for file \"" + fileObject.getPath() + "\" : " + remoteAnalyze);

return listener;
return analyzeRemote(sonarLintEngine, project, clientInputFile, sonarLintHome);
}

Project projectForRules = SonarLintDataManagerUtils.getProjectForAnalyse(dataManager, fileObject);
Expand All @@ -346,7 +291,7 @@ public void setIndeterminate(boolean indeterminate) {
StandaloneAnalysisConfiguration standaloneAnalysisConfiguration =
StandaloneAnalysisConfiguration.builder()
.setBaseDir(new File(sonarLintHome).toPath())
.addInputFiles(clientInputFiles)
.addInputFiles(clientInputFile)
.addExcludedRules(excludedRules)
.addIncludedRules(includedRules)
.addRuleParameters(sonarLintEngine.getRuleParameters(projectForRules))
Expand All @@ -367,6 +312,61 @@ public void setIndeterminate(boolean indeterminate) {
return issues;
}

private static List<Issue> analyzeRemote(SonarLintEngine sonarLintEngine, Project project, FSClientInputFile clientInputFile, String sonarLintHome) {
SonarLintRemoteEngine remoteEngine = Lookup.getDefault().lookup(SonarLintRemoteEngine.class);
SonarLintRemoteProjectConfiguration sonarLintRemoteProjectConfiguration = SonarLintRemoteProjectConfiguration.fromProject(project);
ConnectedAnalysisConfiguration build = ConnectedAnalysisConfiguration.builder()
.setProjectKey(sonarLintRemoteProjectConfiguration.getProjectKey())
.addInputFiles(clientInputFile)
.setBaseDir(new File(sonarLintHome).toPath())
.build();
List<Issue> listener = new ArrayList<>();
AnalysisResults remoteAnalyze = remoteEngine.analyze(
sonarLintRemoteProjectConfiguration,
build,
listener::add,
(String formattedMessage, ClientLogOutput.Level level) -> {
System.out.println(level + " | " + formattedMessage);
},
new ClientProgressMonitor() {
@Override
public boolean isCanceled() {
return false;
}

@Override
public void setMessage(String msg) {
LOG.info("ClientProgressMonitor.setMessage(\"" + msg + "\")");
}

@Override
public void setFraction(float fraction) {
LOG.info("ClientProgressMonitor.setFraction(" + fraction + ")");
}

@Override
public void setIndeterminate(boolean indeterminate) {
LOG.info("ClientProgressMonitor.setIndeterminate(" + indeterminate + ")");
}
}
);

List<ServerIssue> serverIssues = remoteEngine.getServerIssues(sonarLintRemoteProjectConfiguration, clientInputFile.relativePath());
serverIssues.stream()
.map(
s ->
new SonarLintIssueWrapperForServerIssue(
clientInputFile,
s,
// TODO replace by a sonarcloud/sonarqube rules store
sonarLintEngine.getRuleDetails(s.getRuleKey())
)
)
.forEach(listener::add);
LOG.fine(() -> "Analyze (Remote) result for file \"" + clientInputFile.uri() + "\" : " + remoteAnalyze);
return listener;
}

/**
* Retrive stylesheet for HTML rule detail description
* @param sonarLintOptions SonarLint global integration option used to retrieve the stylesheet to apply
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* sonarlint4netbeans: SonarLint integration for Apache Netbeans
* Copyright (C) 2023 Philippe FICHET.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package com.github.philippefichet.sonarlint4netbeans.remote;

import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.sonarsource.sonarlint.shaded.org.apache.commons.codec.binary.Hex;
import org.sonarsource.sonarlint.shaded.org.apache.commons.codec.digest.DigestUtils;
import org.sonarsource.sonarlint.shaded.org.apache.commons.lang.StringUtils;

/**
* Copied from
* https://github.com/SonarSource/sonarqube/blob/master/sonar-core/src/main/java/org/sonar/core/hash/SourceLineHashesComputer.java
*
* @author FICHET Philippe &lt;[email protected]&gt;
*/
public class SourceLineHashesComputer {

private final MessageDigest md5Digest = DigestUtils.getMd5Digest();
private final List<String> lineHashes;

public SourceLineHashesComputer() {
this.lineHashes = new ArrayList<>();
}

public SourceLineHashesComputer(int expectedLineCount) {
this.lineHashes = new ArrayList<>(expectedLineCount);
}

public void addLine(String line) {
Objects.requireNonNull(line, "line can not be null");
lineHashes.add(computeHash(line));
}

public List<String> getLineHashes() {
return Collections.unmodifiableList(lineHashes);
}

private String computeHash(String line) {
String reducedLine = StringUtils.replaceChars(line, "\t ", "");
if (reducedLine.isEmpty()) {
return "";
}
return Hex.encodeHexString(md5Digest.digest(reducedLine.getBytes(UTF_8)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package com.github.philippefichet.sonarlint4netbeans.remote.wrapper;

import com.github.philippefichet.sonarlint4netbeans.FSClientInputFile;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -34,7 +35,9 @@
import org.sonarsource.sonarlint.core.commons.RuleType;
import org.sonarsource.sonarlint.core.commons.SoftwareQuality;
import org.sonarsource.sonarlint.core.commons.TextRange;
import org.sonarsource.sonarlint.core.commons.TextRangeWithHash;
import org.sonarsource.sonarlint.core.commons.VulnerabilityProbability;
import org.sonarsource.sonarlint.core.serverconnection.issues.FileLevelServerIssue;
import org.sonarsource.sonarlint.core.serverconnection.issues.LineLevelServerIssue;
import org.sonarsource.sonarlint.core.serverconnection.issues.RangeLevelServerIssue;
import org.sonarsource.sonarlint.core.serverconnection.issues.ServerIssue;
Expand All @@ -46,13 +49,37 @@
public class SonarLintIssueWrapperForServerIssue implements Issue {

private final ServerIssue serverIssue;
private final ClientInputFile clientInputFile;
private final FSClientInputFile clientInputFile;
private final Optional<StandaloneRuleDetails> ruleDetails;
private final Integer startLine;
private final Integer endLine;

public SonarLintIssueWrapperForServerIssue(ClientInputFile clientInputFile, ServerIssue serverIssue, Optional<StandaloneRuleDetails> ruleDetails) {
public SonarLintIssueWrapperForServerIssue(FSClientInputFile clientInputFile, ServerIssue serverIssue, Optional<StandaloneRuleDetails> ruleDetails) {
this.clientInputFile = clientInputFile;
this.serverIssue = serverIssue;
this.ruleDetails = ruleDetails;

if (serverIssue instanceof FileLevelServerIssue) {
FileLevelServerIssue flsi = (FileLevelServerIssue)serverIssue;
startLine = null;
endLine = null;
} else if (serverIssue instanceof LineLevelServerIssue) {
LineLevelServerIssue llsi = (LineLevelServerIssue)serverIssue;
List<String> lineHashes = clientInputFile.getLineHashes();
// Naive implementation because of possible collision
// TODO improve search if possible (nearest line?)
int indexOf = lineHashes.indexOf(llsi.getLineHash()) + 1;
startLine = indexOf;
endLine = indexOf;
} else if (serverIssue instanceof RangeLevelServerIssue) {
RangeLevelServerIssue rlsi = (RangeLevelServerIssue)serverIssue;
TextRangeWithHash textRange = rlsi.getTextRange();
startLine = textRange.getStartLine();
endLine = textRange.getStartLine();
} else {
startLine = null;
endLine = null;
}
}

@Override
Expand Down Expand Up @@ -118,12 +145,7 @@ public TextRange getTextRange() {

@Override
public Integer getStartLine() {
if (serverIssue instanceof RangeLevelServerIssue) {
return ((RangeLevelServerIssue)serverIssue).getTextRange().getStartLine();
} else if(serverIssue instanceof LineLevelServerIssue){
return ((LineLevelServerIssue)serverIssue).getLine();
}
return null;
return startLine;
}

@Override
Expand All @@ -136,12 +158,7 @@ public Integer getStartLineOffset() {

@Override
public Integer getEndLine() {
if (serverIssue instanceof RangeLevelServerIssue) {
return ((RangeLevelServerIssue)serverIssue).getTextRange().getEndLine();
} else if(serverIssue instanceof LineLevelServerIssue){
return ((LineLevelServerIssue)serverIssue).getLine();
}
return null;
return endLine;
}

@Override
Expand All @@ -163,6 +180,4 @@ public Map<SoftwareQuality, ImpactSeverity> getImpacts() {
// TODO
return Collections.emptyMap();
}


}
Loading

0 comments on commit b81a978

Please sign in to comment.