Skip to content

Commit c313fa7

Browse files
committed
SLVSCODE-152 Show Node-related settings on issue with Node path
1 parent e213cca commit c313fa7

File tree

13 files changed

+158
-159
lines changed

13 files changed

+158
-159
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</issueManagement>
2424

2525
<properties>
26-
<sonarlint.core.version>4.14.1.22501</sonarlint.core.version>
26+
<sonarlint.core.version>4.14.1.22545</sonarlint.core.version>
2727
<!-- analyzers used for tests -->
2828
<sonar.java.version>6.5.0.22421</sonar.java.version>
2929
<sonar.javascript.version>6.2.1.12157</sonar.javascript.version>
@@ -209,7 +209,7 @@
209209
<configuration>
210210
<trimStackTrace>false</trimStackTrace>
211211
<environmentVariables>
212-
<PATH>${basedir}/src/test/resources/fake-ts-project/node;${env.PATH}</PATH>
212+
<PATH>${basedir}/src/test/resources/fake-ts-project/node${path.separator}${env.PATH}</PATH>
213213
</environmentVariables>
214214
</configuration>
215215
</plugin>

src/main/java/org/sonarsource/sonarlint/ls/AnalysisManager.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.eclipse.lsp4j.Range;
5454
import org.sonar.api.utils.log.Logger;
5555
import org.sonar.api.utils.log.Loggers;
56+
import org.sonarsource.sonarlint.core.client.api.common.Language;
5657
import org.sonarsource.sonarlint.core.client.api.common.PluginDetails;
5758
import org.sonarsource.sonarlint.core.client.api.common.analysis.AnalysisResults;
5859
import org.sonarsource.sonarlint.core.client.api.common.analysis.ClientInputFile;
@@ -248,7 +249,10 @@ private void analyze(URI fileUri, boolean shouldFetchServerIssues) {
248249
}
249250
SkippedPluginsNotifier.notifyOnceForSkippedPlugins(analysisResults.results, analysisResults.allPlugins, client);
250251

251-
telemetry.analysisDoneOnSingleLanguage(analysisResults.results.languagePerFile().values().iterator().next(), analysisResults.analysisTime);
252+
Collection<Language> analyzedLanguages = analysisResults.results.languagePerFile().values();
253+
if (!analyzedLanguages.isEmpty()) {
254+
telemetry.analysisDoneOnSingleLanguage(analyzedLanguages.iterator().next(), analysisResults.analysisTime);
255+
}
252256

253257
// Ignore files with parsing error
254258
analysisResults.results.failedAnalysisFiles().stream()
@@ -504,12 +508,6 @@ private void analyzeAllOpenJavaFiles() {
504508
}
505509
}
506510

507-
private void analyzeAllOpenFiles() {
508-
for (URI fileUri : fileContentPerFileURI.keySet()) {
509-
analyzeAsync(fileUri, false);
510-
}
511-
}
512-
513511
private Map<String, String> configureJavaProperties(URI fileUri) {
514512
Optional<GetJavaConfigResponse> cachedJavaConfigOpt = ofNullable(javaConfigPerFileURI.get(fileUri)).orElse(empty());
515513
return cachedJavaConfigOpt.map(cachedJavaConfig -> {

src/main/java/org/sonarsource/sonarlint/ls/DefaultClientInputFile.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private static Language toSqLanguage(@Nullable String clientLanguageId) {
9797
if (clientLanguageId == null) {
9898
return null;
9999
}
100-
// See https://microsoft.github.io/language-server-protocol/specification#textdocumentitem
100+
// See https://microsoft.github.io/language-server-protocol/specification#textDocumentItem
101101
switch (clientLanguageId) {
102102
case "javascript":
103103
case "javascriptreact":
@@ -114,8 +114,13 @@ private static Language toSqLanguage(@Nullable String clientLanguageId) {
114114
return Language.HTML;
115115
case "oraclesql":
116116
return Language.PLSQL;
117+
case "apex":
118+
case "apex-anon":
119+
// See https://github.com/forcedotcom/salesforcedx-vscode/blob/5e4b7715d1cb3d1ee2780780ed63f70f58e93b20/packages/salesforcedx-vscode-apex/package.json#L273
120+
return Language.APEX;
117121
default:
118-
return null;
122+
// Other supported languages map to the same key as the one used in SonarQube/SonarCloud
123+
return Language.forKey(clientLanguageId).orElse(null);
119124
}
120125
}
121126
}

src/main/java/org/sonarsource/sonarlint/ls/NodeJsRuntime.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,16 @@
2121

2222
import java.nio.file.Path;
2323
import java.nio.file.Paths;
24-
import java.util.Objects;
2524
import java.util.Optional;
2625
import java.util.function.Supplier;
27-
import javax.annotation.CheckForNull;
2826
import javax.annotation.Nullable;
2927
import org.apache.commons.lang.StringUtils;
3028
import org.sonarsource.sonarlint.core.NodeJsHelper;
3129
import org.sonarsource.sonarlint.core.client.api.common.Version;
3230
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
3331
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettings;
34-
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettingsChangeListener;
3532

36-
public class NodeJsRuntime implements WorkspaceSettingsChangeListener {
33+
public class NodeJsRuntime {
3734

3835
private final SettingsManager settingsManager;
3936
private final Supplier<NodeJsHelper> nodeJsHelperFactory;
@@ -82,13 +79,4 @@ public Version getNodeJsVersion() {
8279
}
8380
return nodeJsVersion;
8481
}
85-
86-
@Override
87-
public void onChange(@CheckForNull WorkspaceSettings oldValue, WorkspaceSettings newValue) {
88-
if (oldValue == null || !(Objects.equals(oldValue.pathToNodeExecutable(), newValue.pathToNodeExecutable()))) {
89-
init = false;
90-
nodeJsPath = null;
91-
nodeJsVersion = null;
92-
}
93-
}
9482
}

src/main/java/org/sonarsource/sonarlint/ls/SkippedPluginsNotifier.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
*/
2020
package org.sonarsource.sonarlint.ls;
2121

22+
import com.google.common.annotations.VisibleForTesting;
2223
import java.util.Collection;
2324
import java.util.HashSet;
2425
import java.util.Objects;
2526
import java.util.Optional;
2627
import java.util.Set;
28+
import java.util.concurrent.CompletableFuture;
29+
import java.util.function.Supplier;
2730
import org.eclipse.lsp4j.MessageActionItem;
28-
import org.eclipse.lsp4j.MessageParams;
2931
import org.eclipse.lsp4j.MessageType;
3032
import org.eclipse.lsp4j.ShowMessageRequestParams;
3133
import org.sonarsource.sonarlint.core.client.api.common.Language;
@@ -40,6 +42,8 @@ public class SkippedPluginsNotifier {
4042

4143
private static final Set<String> displayedMessages = new HashSet<>();
4244

45+
public static final MessageActionItem ACTION_OPEN_SETTINGS = new MessageActionItem("Open Settings");
46+
4347
private SkippedPluginsNotifier() {
4448
}
4549

@@ -58,36 +62,30 @@ public static void notifyOnceForSkippedPlugins(AnalysisResults analysisResults,
5862
String content = String.format(
5963
"Java runtime version %s or later is required. Current version is %s.",runtimeRequirement.getMinVersion(), runtimeRequirement.getCurrentVersion()
6064
);
61-
openJavaSettingsRequest(client, formatMessage(title, content));
65+
showMessageWithOpenSettingsAction(client, formatMessage(title, content), client::openJavaHomeSettings);
6266
} else if (runtimeRequirement.getRuntime() == SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement.NODEJS) {
6367
String content = String.format(
6468
"Node.js runtime version %s or later is required.", runtimeRequirement.getMinVersion());
6569
if (runtimeRequirement.getCurrentVersion() != null) {
6670
content += String.format(" Current version is %s.", runtimeRequirement.getCurrentVersion());
6771
}
68-
// TODO Show link to Node path property in the settings
69-
showMessageWithoutAction(client, formatMessage(title, content));
72+
showMessageWithOpenSettingsAction(client, formatMessage(title, content), client::openPathToNodeSettings);
7073
}
7174
}
7275
});
7376
});
7477
}
7578

76-
private static void openJavaSettingsRequest(SonarLintExtendedLanguageClient client, String message) {
79+
private static void showMessageWithOpenSettingsAction(SonarLintExtendedLanguageClient client, String message, Supplier<CompletableFuture<Void>> callback) {
7780
if (displayedMessages.add(message)) {
78-
ShowMessageRequestParams params = new ShowMessageRequestParams(singletonList(new MessageActionItem("Open Java Settings")));
81+
ShowMessageRequestParams params = new ShowMessageRequestParams(singletonList(ACTION_OPEN_SETTINGS));
7982
params.setType(MessageType.Error);
8083
params.setMessage(message);
81-
client.showMessageRequest(params).thenAccept(action -> client.openJavaHomeSettings());
82-
}
83-
}
84-
85-
private static void showMessageWithoutAction(SonarLintExtendedLanguageClient client, String longMessage) {
86-
if (displayedMessages.add(longMessage)) {
87-
MessageParams params = new MessageParams();
88-
params.setType(MessageType.Error);
89-
params.setMessage(longMessage);
90-
client.showMessage(params);
84+
client.showMessageRequest(params).thenAccept(action -> {
85+
if (ACTION_OPEN_SETTINGS.equals(action)) {
86+
callback.get();
87+
}
88+
});
9189
}
9290
}
9391

@@ -103,4 +101,9 @@ private static void showMessageWithoutAction(SonarLintExtendedLanguageClient cli
103101
private static String formatMessage(String title, String content) {
104102
return String.format("%s: %s", title, content);
105103
}
104+
105+
@VisibleForTesting
106+
static void clearMessages() {
107+
displayedMessages.clear();
108+
}
106109
}

src/main/java/org/sonarsource/sonarlint/ls/SonarLintExtendedLanguageClient.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public interface SonarLintExtendedLanguageClient extends LanguageClient {
3838
@JsonRequest("sonarlint/openJavaHomeSettings")
3939
CompletableFuture<Void> openJavaHomeSettings();
4040

41+
@JsonRequest("sonarlint/openPathToNodeSettings")
42+
CompletableFuture<Void> openPathToNodeSettings();
43+
4144
@JsonRequest("sonarlint/showRuleDescription")
4245
CompletableFuture<Void> showRuleDescription(ShowRuleDescriptionParams params);
4346

src/main/java/org/sonarsource/sonarlint/ls/SonarLintLanguageServer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ public class SonarLintLanguageServer implements SonarLintExtendedLanguageServer,
107107
this.workspaceFoldersManager = new WorkspaceFoldersManager();
108108
this.settingsManager = new SettingsManager(this.client, this.workspaceFoldersManager);
109109
this.nodeJsRuntime = new NodeJsRuntime(settingsManager);
110-
this.settingsManager.addListener(nodeJsRuntime);
111110
this.enginesFactory = new EnginesFactory(analyzers, lsLogOutput, nodeJsRuntime);
112111
this.settingsManager.addListener(telemetry);
113112
this.settingsManager.addListener(lsLogOutput);

src/main/java/org/sonarsource/sonarlint/ls/Utils.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@
2222
import com.google.gson.Gson;
2323
import com.google.gson.JsonElement;
2424
import com.google.gson.JsonSyntaxException;
25-
import java.util.Iterator;
2625
import java.util.Map;
27-
import java.util.Set;
2826
import java.util.concurrent.ThreadFactory;
2927
import javax.annotation.CheckForNull;
3028
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
3129
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
3230
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
3331
import org.sonar.api.utils.log.Logger;
3432
import org.sonar.api.utils.log.Loggers;
35-
import org.sonarsource.sonarlint.core.client.api.common.Language;
3633

3734
public class Utils {
3835

@@ -65,15 +62,4 @@ public static void interrupted(InterruptedException e) {
6562
LOG.debug("Interrupted!", e);
6663
Thread.currentThread().interrupt();
6764
}
68-
69-
public static Language[] toLanguageArray(Set<Language> languages) {
70-
Language[] languagesArray = new Language[languages.size()];
71-
Iterator<Language> iterator = languages.iterator();
72-
for (int i = 0; i < languages.size(); i++) {
73-
languagesArray[i] = iterator.next();
74-
}
75-
return languagesArray;
76-
}
77-
78-
7965
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* SonarLint Language Server
3+
* Copyright (C) 2009-2020 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.ls;
21+
22+
23+
import java.util.stream.Stream;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.sonarsource.sonarlint.core.client.api.common.Language;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
class DefaultClientInputFileTest {
32+
33+
@ParameterizedTest(name = "Should detect {0} as {1}")
34+
@MethodSource("provideParametersForLanguageDetection")
35+
void shouldDetectLanguage(String clientLanguageId, Language expected) {
36+
assertThat(new DefaultClientInputFile(null, null, "", false, clientLanguageId).language())
37+
.isEqualTo(expected);
38+
}
39+
40+
private static Stream<Arguments> provideParametersForLanguageDetection() {
41+
return Stream.of(
42+
Arguments.of("javascript", Language.JS),
43+
Arguments.of("javascriptreact", Language.JS),
44+
Arguments.of("vue", Language.JS),
45+
Arguments.of("vue component", Language.JS),
46+
Arguments.of("babel es6 javascript", Language.JS),
47+
48+
Arguments.of("python", Language.PYTHON),
49+
50+
Arguments.of("typescript", Language.TS),
51+
Arguments.of("typescriptreact", Language.TS),
52+
53+
Arguments.of("html", Language.HTML),
54+
55+
Arguments.of("oraclesql", Language.PLSQL),
56+
Arguments.of("plsql", Language.PLSQL),
57+
58+
Arguments.of("apex", Language.APEX),
59+
Arguments.of("apex-anon", Language.APEX),
60+
61+
Arguments.of("php", Language.PHP),
62+
Arguments.of("java", Language.JAVA),
63+
64+
Arguments.of("unknown", null)
65+
);
66+
}
67+
}

src/test/java/org/sonarsource/sonarlint/ls/NodeJsRuntimeTest.java

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@
1919
*/
2020
package org.sonarsource.sonarlint.ls;
2121

22-
import java.io.File;
23-
import java.nio.file.Files;
2422
import java.nio.file.Path;
2523
import org.junit.jupiter.api.BeforeEach;
2624
import org.junit.jupiter.api.Test;
2725
import org.junit.jupiter.api.io.TempDir;
28-
import org.mockito.Mockito;
2926
import org.sonarsource.sonarlint.core.NodeJsHelper;
3027
import org.sonarsource.sonarlint.core.client.api.common.Version;
3128
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
3229
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettings;
3330

3431
import static org.assertj.core.api.Assertions.assertThat;
35-
import static org.mockito.Mockito.*;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.times;
34+
import static org.mockito.Mockito.verify;
35+
import static org.mockito.Mockito.when;
3636

3737
class NodeJsRuntimeTest {
3838

@@ -82,47 +82,4 @@ void shouldLazyInitializeWithNodeSettings() {
8282
verify(settings).pathToNodeExecutable();
8383
verify(nodeJsHelper, times(1)).detect(temp.getFileName());
8484
}
85-
86-
@Test
87-
void shouldKeepOldSettingsWhenNotChanged() {
88-
when(settings.pathToNodeExecutable()).thenReturn(null);
89-
WorkspaceSettings newSettings = mock(WorkspaceSettings.class);
90-
when(newSettings.pathToNodeExecutable()).thenReturn(null);
91-
92-
assertThat(underTest.nodeVersion()).isNull();
93-
assertThat(underTest.getNodeJsPath()).isNull();
94-
assertThat(underTest.getNodeJsVersion()).isNull();
95-
96-
underTest.onChange(settings, newSettings);
97-
98-
assertThat(underTest.nodeVersion()).isNull();
99-
assertThat(underTest.getNodeJsPath()).isNull();
100-
assertThat(underTest.getNodeJsVersion()).isNull();
101-
102-
verify(nodeJsHelper, times(1)).detect(null);
103-
}
104-
105-
@Test
106-
void shouldRedetectWhenNodePathChanges() {
107-
when(settings.pathToNodeExecutable()).thenReturn(null);
108-
WorkspaceSettings newSettings = mock(WorkspaceSettings.class);
109-
String newPathToNodeSettings = temp.getFileName().toString();
110-
when(newSettings.pathToNodeExecutable()).thenReturn(newPathToNodeSettings);
111-
112-
assertThat(underTest.nodeVersion()).isNull();
113-
assertThat(underTest.getNodeJsPath()).isNull();
114-
assertThat(underTest.getNodeJsVersion()).isNull();
115-
116-
underTest.onChange(settings, newSettings);
117-
118-
when(settings.pathToNodeExecutable()).thenReturn(newPathToNodeSettings);
119-
when(nodeJsHelper.getNodeJsPath()).thenReturn(temp.resolve("node"));
120-
String version = "12.34.56";
121-
when(nodeJsHelper.getNodeJsVersion()).thenReturn(Version.create(version));
122-
123-
assertThat(underTest.nodeVersion()).isEqualTo(version);
124-
125-
verify(nodeJsHelper, times(1)).detect(null);
126-
verify(nodeJsHelper, times(1)).detect(temp.getFileName());
127-
}
12885
}

0 commit comments

Comments
 (0)