Skip to content

Commit 01624d6

Browse files
authored
Merge pull request #261 from snyk/feat/IDE-976_apply_fix_command
feat: apply fix command [IDE-976]
2 parents 529d605 + e7ca24c commit 01624d6

File tree

7 files changed

+154
-86
lines changed

7 files changed

+154
-86
lines changed

plugin/src/main/java/io/snyk/eclipse/plugin/html/BaseHtmlProvider.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ public String replaceCssVariables(String html) {
119119
getColorAsHex("org.eclipse.ui.workbench.INACTIVE_TAB_BG_START", "#F0F0F0"));
120120
htmlStyled = htmlStyled.replace("var(--circle-color)",
121121
getColorAsHex("org.eclipse.ui.workbench.INACTIVE_TAB_BG_START", "#F0F0F0"));
122-
123122
htmlStyled = htmlStyled.replace("var(--border-color)",
124123
getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_OUTER_KEYLINE_COLOR", "#CCCCCC"));
124+
htmlStyled = htmlStyled.replace("var(--input-border)",
125+
getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_OUTER_KEYLINE_COLOR", "#CCCCCC"));
125126
htmlStyled = htmlStyled.replace("var(--link-color)", getColorAsHex("ACTIVE_HYPERLINK_COLOR", "#0000FF"));
126127
htmlStyled = htmlStyled.replace("var(--horizontal-border-color)",
127128
getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_OUTER_KEYLINE_COLOR", "#CCCCCC"));

plugin/src/main/java/io/snyk/eclipse/plugin/html/CodeHtmlProvider.java

+73-54
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import io.snyk.eclipse.plugin.preferences.Preferences;
44

55
public class CodeHtmlProvider extends BaseHtmlProvider {
6-
private static CodeHtmlProvider instance = new CodeHtmlProvider();
6+
private static CodeHtmlProvider instance = new CodeHtmlProvider();
77

88
public static CodeHtmlProvider getInstance() {
99
synchronized (CodeHtmlProvider.class) {
@@ -16,61 +16,80 @@ public static CodeHtmlProvider getInstance() {
1616
return instance;
1717
}
1818

19-
@Override
20-
public String getInitScript() {
21-
String themeScript = getThemeScript();
22-
String initScript = super.getInitScript();
23-
return initScript + "\n" + """
24-
function navigateToIssue(e, target) {
25-
e.preventDefault();
26-
var filePath = target.getAttribute('file-path');
27-
var startLine = target.getAttribute('start-line');
28-
var endLine = target.getAttribute('end-line');
29-
var startCharacter = target.getAttribute('start-character');
30-
var endCharacter = target.getAttribute('end-character');
31-
window.openInEditor(filePath, startLine, endLine, startCharacter, endCharacter);
32-
}
33-
var navigatableLines = document.getElementsByClassName('data-flow-clickable-row');
34-
for(var i = 0; i < navigatableLines.length; i++) {
35-
navigatableLines[i].onclick = function(e) {
36-
navigateToIssue(e, this);
37-
return false;
38-
};
39-
}
40-
if(document.getElementById('position-line')) {
41-
document.getElementById('position-line').onclick = function(e) {
42-
var target = navigatableLines[0];
43-
if(target) {
44-
navigateToIssue(e, target);
45-
}
46-
}
47-
}
48-
// Disable AIfix
49-
if(document.getElementById('ai-fix-wrapper') && document.getElementById('no-ai-fix-wrapper')){
50-
document.getElementById('ai-fix-wrapper').className = 'hidden';
51-
document.getElementById('no-ai-fix-wrapper').className = '';
52-
}
53-
""" + themeScript;
54-
}
19+
@Override
20+
public String getInitScript() {
21+
String themeScript = getThemeScript();
22+
String initScript = super.getInitScript();
23+
return initScript + "\n" + """
24+
function navigateToIssue(e, target) {
25+
e.preventDefault();
26+
var filePath = target.getAttribute('file-path');
27+
var startLine = target.getAttribute('start-line');
28+
var endLine = target.getAttribute('end-line');
29+
var startCharacter = target.getAttribute('start-character');
30+
var endCharacter = target.getAttribute('end-character');
31+
window.openInEditor(filePath, startLine, endLine, startCharacter, endCharacter);
32+
}
33+
var navigatableLines = document.getElementsByClassName('data-flow-clickable-row');
34+
for(var i = 0; i < navigatableLines.length; i++) {
35+
navigatableLines[i].onclick = function(e) {
36+
navigateToIssue(e, this);
37+
return false;
38+
};
39+
}
40+
if(document.getElementById('position-line')) {
41+
document.getElementById('position-line').onclick = function(e) {
42+
var target = navigatableLines[0];
43+
if(target) {
44+
navigateToIssue(e, target);
45+
}
46+
}
47+
}
48+
""" + themeScript;
49+
}
50+
51+
private String getThemeScript() {
52+
if (Preferences.getInstance().isTest()) {
53+
return "";
54+
}
55+
56+
String themeScript = "var isDarkTheme = " + isDarkTheme() + ";\n"
57+
+ "document.body.classList.add(isDarkTheme ? 'dark' : 'light');";
58+
return themeScript;
59+
}
60+
61+
@Override
62+
public String replaceCssVariables(String html) {
63+
String htmlStyled = super.replaceCssVariables(html);
64+
65+
// Replace CSS variables with actual color values
66+
htmlStyled = htmlStyled.replace("var(--example-line-removed-color)",
67+
super.getColorAsHex("DELETION_COLOR", "#ff0000"));
68+
htmlStyled = htmlStyled.replace("var(--example-line-added-color)",
69+
super.getColorAsHex("ADDITION_COLOR", "#00ff00"));
70+
htmlStyled = htmlStyled.replace("var(--generated-ai-fix-button-background-color)",
71+
super.getColorAsHex("BUTTON_COLOR", "#375578"));
72+
htmlStyled = htmlStyled.replace("var(--disabled-background-color)",
73+
super.getColorAsHex("org.eclipse.ui.workbench.ACTIVE_TAB_OUTER_KEYLINE_COLOR", "#CCCCCC"));
5574

56-
private String getThemeScript() {
57-
if(Preferences.getInstance().isTest()) {
58-
return "";
59-
}
75+
String htmlWithScripts = replaceAIFixScripts(htmlStyled);
6076

61-
String themeScript = "var isDarkTheme = " + isDarkTheme() + ";\n" +
62-
"document.body.classList.add(isDarkTheme ? 'dark' : 'light');";
63-
return themeScript;
64-
}
77+
return htmlWithScripts;
78+
}
79+
80+
private String replaceAIFixScripts(String html) {
81+
String htmlWithAiFixScripts = html.replace("${ideGenerateAIFix}", getGenerateAiFixScript());
82+
htmlWithAiFixScripts = htmlWithAiFixScripts.replace("${ideApplyAIFix}", getApplyAiFixScript());
83+
84+
return htmlWithAiFixScripts;
85+
}
6586

66-
@Override
67-
public String replaceCssVariables(String html) {
68-
String htmlStyled = super.replaceCssVariables(html);
69-
70-
// Replace CSS variables with actual color values
71-
htmlStyled = htmlStyled.replace("var(--example-line-removed-color)", super.getColorAsHex("DELETION_COLOR", "#ff0000"));
72-
htmlStyled = htmlStyled.replace("var(--example-line-added-color)", super.getColorAsHex("ADDITION_COLOR", "#00ff00"));
87+
private String getGenerateAiFixScript() {
88+
return "window.ideGenAIFix(generateFixQueryString)\n;";
89+
}
90+
91+
private String getApplyAiFixScript() {
92+
return "window.ideApplyFix(fixId);\n";
93+
}
7394

74-
return htmlStyled;
75-
}
7695
}

plugin/src/main/java/io/snyk/eclipse/plugin/views/snyktoolview/BrowserHandler.java

+35-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.apache.commons.lang3.StringUtils.isEmpty;
44

55
import java.nio.file.Paths;
6+
import java.util.Collections;
67
import java.util.concurrent.CompletableFuture;
78

89
import org.eclipse.core.commands.common.CommandException;
@@ -31,6 +32,7 @@
3132
import io.snyk.eclipse.plugin.utils.SnykLogger;
3233
import io.snyk.eclipse.plugin.views.snyktoolview.handlers.IHandlerCommands;
3334
import io.snyk.eclipse.plugin.wizards.SnykWizard;
35+
import io.snyk.languageserver.protocolextension.SnykExtendedLanguageClient;
3436

3537
@SuppressWarnings("restriction")
3638
public class BrowserHandler {
@@ -84,17 +86,44 @@ public Object function(Object[] arguments) {
8486
new BrowserFunction(browser, "stopScan") {
8587
@Override
8688
public Object function(Object[] arguments) {
87-
IHandlerService handlerService =
88-
(IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
89+
IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench()
90+
.getService(IHandlerService.class);
91+
8992
try {
9093
handlerService.executeCommand(IHandlerCommands.STOP_SCAN, null);
9194
} catch (CommandException e) {
9295
SnykLogger.logError(e);
93-
}
96+
}
9497
return null;
9598
}
9699
};
97100

101+
new BrowserFunction(browser, "ideGenAIFix") {
102+
@Override
103+
public Object function(Object[] arguments) {
104+
String params = (String) arguments[0];
105+
String[] parts = params.split("@|@");
106+
String folderURI = (String) parts[0];
107+
String fileURI = (String) parts[2];
108+
String issueID = (String) parts[4];
109+
110+
SnykExtendedLanguageClient.getInstance().sendCodeFixDiffsCommand(folderURI, fileURI, issueID);
111+
112+
return Collections.emptyList();
113+
}
114+
};
115+
116+
new BrowserFunction(browser, "ideApplyFix") {
117+
@Override
118+
public Object function(Object[] arguments) {
119+
String fixId = (String) arguments[0];
120+
121+
SnykExtendedLanguageClient.getInstance().sendCodeApplyAiFixEditCommand(fixId);
122+
123+
return Collections.emptyList();
124+
}
125+
};
126+
98127
browser.addLocationListener(new LocationListener() {
99128
@Override
100129
public void changing(LocationEvent event) {
@@ -158,7 +187,7 @@ public CompletableFuture<Void> updateBrowserContent(TreeNode node) {
158187
}
159188

160189
final var browserContent = htmlProvider.replaceCssVariables(htmlContent);
161-
190+
162191
Display.getDefault().syncExec(() -> {
163192
browser.setText(browserContent);
164193
});
@@ -191,7 +220,8 @@ public String generateHtmlContent(String text) {
191220
}
192221

193222
public void setDefaultBrowserText() {
194-
// If we are not authenticated, show the welcome page, else show the issue placeholder.
223+
// If we are not authenticated, show the welcome page, else show the issue
224+
// placeholder.
195225
if (Preferences.getInstance().getAuthToken().isBlank()) {
196226
browser.setText(StaticPageHtmlProvider.getInstance().getInitHtml());
197227
} else {

plugin/src/main/java/io/snyk/languageserver/LsConstants.java

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ private LsConstants() {
1717
public static final String COMMAND_REPORT_ANALYTICS = "snyk.reportAnalytics";
1818
public static final String COMMAND_GET_FEATURE_FLAG_STATUS = "snyk.getFeatureFlagStatus";
1919
public static final String COMMAND_CODE_FIX_DIFFS = "snyk.code.fixDiffs";
20+
public static final String COMMAND_CODE_FIX_APPLY_AI_EDIT = "snyk.code.fixApplyEdit";
2021
public static final String COMMAND_CODE_SUBMIT_FIX_FEEDBACK = "snyk.code.submitFixFeedback";
2122
public static final String COMMAND_SNYK_CLI = "snyk.executeCLI";
2223
public static final String SNYK_HAS_AUTHENTICATED = "$/snyk.hasAuthenticated";

plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykExtendedLanguageClient.java

+31-25
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import io.snyk.languageserver.SnykLanguageServer;
9797
import io.snyk.languageserver.protocolextension.messageObjects.Diagnostic316;
9898
import io.snyk.languageserver.protocolextension.messageObjects.FeatureFlagStatus;
99+
import io.snyk.languageserver.protocolextension.messageObjects.Fix;
99100
import io.snyk.languageserver.protocolextension.messageObjects.FolderConfig;
100101
import io.snyk.languageserver.protocolextension.messageObjects.FolderConfigsParam;
101102
import io.snyk.languageserver.protocolextension.messageObjects.HasAuthenticatedParam;
@@ -333,6 +334,14 @@ public String getIssueDescription(String issueId) {
333334
return String.valueOf(result);
334335
}
335336

337+
public void sendCodeFixDiffsCommand(String folderURI, String fileURI, String issueID) {
338+
executeCommand(LsConstants.COMMAND_CODE_FIX_DIFFS, List.of(folderURI, fileURI, issueID));
339+
}
340+
341+
public void sendCodeApplyAiFixEditCommand(String fixId) {
342+
executeCommand(LsConstants.COMMAND_CODE_FIX_APPLY_AI_EDIT, List.of(fixId));
343+
}
344+
336345
@JsonNotification(value = LsConstants.SNYK_HAS_AUTHENTICATED)
337346
public void hasAuthenticated(HasAuthenticatedParam param) {
338347
var prefs = Preferences.getInstance();
@@ -477,7 +486,7 @@ public CompletableFuture<ShowDocumentResult> showDocument(
477486
return new ShowDocumentResult(true);
478487
});
479488
}
480-
489+
481490
private Issue getIssueFromCache(String filePath, String issueId) {
482491
SnykIssueCache issueCache = getIssueCache(filePath);
483492
return issueCache.getCodeSecurityIssuesForPath(filePath).stream()
@@ -740,6 +749,18 @@ public CompletableFuture<Void> createProgress(
740749
return super.createProgress(params);
741750
}
742751

752+
@Override
753+
public void notifyProgress(final ProgressParams params) {
754+
if (params.getValue() == null) {
755+
return;
756+
}
757+
WorkDoneProgressNotification progressNotification = params.getValue().getLeft();
758+
if (progressNotification != null && progressNotification.getKind() == WorkDoneProgressKind.end) {
759+
this.progressManager.removeProgress(params.getToken().getLeft());
760+
}
761+
super.notifyProgress(params);
762+
}
763+
743764
/**
744765
* Refresh the token using language server. Waits up to 2s for the token
745766
* change.
@@ -811,10 +832,6 @@ public static <T> T convertInstanceOfObject(Object o, Class<T> clazz) {
811832
}
812833
}
813834

814-
public void setToolWindow(ISnykToolView toolView) {
815-
this.toolView = toolView;
816-
}
817-
818835
public void clearCache() {
819836
List<IProject> openProjects = ResourceUtils
820837
.getAccessibleTopLevelProjects();
@@ -831,24 +848,6 @@ public void clearCache() {
831848

832849
}
833850

834-
public void setProgressMgr(ProgressManager progressMgr) {
835-
this.progressManager = progressMgr;
836-
}
837-
838-
@Override
839-
public void notifyProgress(final ProgressParams params) {
840-
if (params.getValue() == null) {
841-
return;
842-
}
843-
WorkDoneProgressNotification progressNotification = params.getValue()
844-
.getLeft();
845-
if (progressNotification != null
846-
&& progressNotification.getKind() == WorkDoneProgressKind.end) {
847-
this.progressManager.removeProgress(params.getToken().getLeft());
848-
}
849-
super.notifyProgress(params);
850-
}
851-
852851
@JsonRequest(value = "workspace/snyk.sdks")
853852
public CompletableFuture<List<LsSdk>> getSdks(
854853
WorkspaceFolder workspaceFolder) {
@@ -861,12 +860,19 @@ public CompletableFuture<List<LsSdk>> getSdks(
861860
});
862861
}
863862

864-
public ProgressManager getProgressManager() {
865-
return this.progressManager;
863+
public void setToolWindow(ISnykToolView toolView) {
864+
this.toolView = toolView;
866865
}
867866

868867
public void setLs(LanguageServer ls) {
869868
this.ls = ls;
870869
}
871870

871+
public void setProgressMgr(ProgressManager progressMgr) {
872+
this.progressManager = progressMgr;
873+
}
874+
875+
public ProgressManager getProgressManager() {
876+
return this.progressManager;
877+
}
872878
}

plugin/src/main/java/io/snyk/languageserver/protocolextension/SnykUriUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ static Map<String, String> getQueryParameters(String queryString) {
3838
if (!param.isEmpty()) {
3939
String[] keyValue = param.split("=");
4040

41-
if (keyValue.length == 2) { // TODO add the no pmd here
41+
if (keyValue.length == 2) { // NOPMD, AvoidLiteralsInIfCondition
4242
try {
4343
paramMap.put(keyValue[0],
4444
URLDecoder.decode(keyValue[1], "UTF-8"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.snyk.languageserver.protocolextension.messageObjects;
2+
3+
import java.util.Map;
4+
5+
import com.google.gson.annotations.SerializedName;
6+
7+
public record Fix(
8+
@SerializedName("fixId") String fixId,
9+
@SerializedName("unifiedDiffsPerFile") Map<String, String> unifiedDiffsPerFile) {
10+
// no-arg constructor is generated automatically by Java compiler
11+
}

0 commit comments

Comments
 (0)