diff --git a/pom.xml b/pom.xml index 185179f94..5de8c8a6d 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 17 - 10.11.0.79608 + 10.11.0.79653 1.6.10 diff --git a/src/main/java/org/sonarsource/sonarlint/ls/SonarLintLanguageServer.java b/src/main/java/org/sonarsource/sonarlint/ls/SonarLintLanguageServer.java index f5913efc9..aea662a9c 100644 --- a/src/main/java/org/sonarsource/sonarlint/ls/SonarLintLanguageServer.java +++ b/src/main/java/org/sonarsource/sonarlint/ls/SonarLintLanguageServer.java @@ -482,7 +482,7 @@ public void didChange(DidChangeTextDocumentParams params) { } else { // VSCode sends us full file content in the change event CompletableFutures.computeAsync(cancelChecker -> { - moduleEventsProcessor.notifyBackendWithFileLanguageAndContent(file.get()); + moduleEventsProcessor.notifyBackendWithUpdatedContent(file.get()); return null; }); } @@ -587,7 +587,7 @@ public void didChange(DidChangeNotebookDocumentParams params) { } else { var file = openNotebook.get().asVersionedOpenFile(); CompletableFutures.computeAsync(cancelChecker -> { - moduleEventsProcessor.notifyBackendWithFileLanguageAndContent(file); + moduleEventsProcessor.notifyBackendWithUpdatedContent(file); return null; }); } diff --git a/src/main/java/org/sonarsource/sonarlint/ls/backend/BackendService.java b/src/main/java/org/sonarsource/sonarlint/ls/backend/BackendService.java index d0210dabe..b84c9c8aa 100644 --- a/src/main/java/org/sonarsource/sonarlint/ls/backend/BackendService.java +++ b/src/main/java/org/sonarsource/sonarlint/ls/backend/BackendService.java @@ -346,8 +346,8 @@ public CompletableFuture getAllProjects(Either deletedFileUris, List addedOrChangedFiles) { - initializedBackend().getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(deletedFileUris, addedOrChangedFiles)); + public void updateFileSystem(List addedFiles, List changedFiles, List deletedFileUris) { + initializedBackend().getFileService().didUpdateFileSystem(new DidUpdateFileSystemParams(addedFiles, changedFiles, deletedFileUris)); } public CompletableFuture getAllTaints(String folderUri) { diff --git a/src/main/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessor.java b/src/main/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessor.java index 6e745806e..884f788ff 100644 --- a/src/main/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessor.java +++ b/src/main/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessor.java @@ -27,8 +27,10 @@ import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.lsp4j.FileChangeType; import org.eclipse.lsp4j.FileEvent; +import org.jetbrains.annotations.NotNull; import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto; import org.sonarsource.sonarlint.core.rpc.protocol.common.Language; @@ -70,7 +72,8 @@ public void didChangeWatchedFiles(List changes) { private void notifyBackend(List changes) { List deletedFileUris = new ArrayList<>(); - List addedOrChangedFiles = new ArrayList<>(); + List addedFiles = new ArrayList<>(); + List changedFiles = new ArrayList<>(); changes.forEach(event -> { var fileUri = URI.create(event.getUri()); if (event.getType() == FileChangeType.Deleted) { @@ -84,15 +87,32 @@ private void notifyBackend(List changes) { var relativePath = baseDir.relativize(fsPath); var folderUri = folder.getUri().toString(); var isTest = isTestFile(fileUri, settings); - addedOrChangedFiles.add(new ClientFileDto(fileUri, relativePath, folderUri, isTest, StandardCharsets.UTF_8.name(), fsPath, null, null, true)); + if (event.getType() == FileChangeType.Created) { + addedFiles.add(new ClientFileDto(fileUri, relativePath, folderUri, isTest, StandardCharsets.UTF_8.name(), fsPath, null, null, true)); + } else { + changedFiles.add(new ClientFileDto(fileUri, relativePath, folderUri, isTest, StandardCharsets.UTF_8.name(), fsPath, null, null, true)); + } }); } }); - backendServiceFacade.getBackendService().updateFileSystem(deletedFileUris, addedOrChangedFiles); + backendServiceFacade.getBackendService().updateFileSystem(addedFiles, changedFiles, deletedFileUris); } public void notifyBackendWithFileLanguageAndContent(VersionedOpenFile file) { - List filesToNotify = new ArrayList<>(); + var openedFileDto = getClientFileDto(file); + // We are simply enriching already added files with language and content information; The files were not actually modified + // i.e. didOpen + backendServiceFacade.getBackendService().updateFileSystem(List.of(openedFileDto), List.of(), List.of()); + } + + public void notifyBackendWithUpdatedContent(VersionedOpenFile file) { + var changedFileDto = getClientFileDto(file); + backendServiceFacade.getBackendService().updateFileSystem(List.of(), List.of(changedFileDto), List.of()); + } + + @NotNull + ClientFileDto getClientFileDto(VersionedOpenFile file) { + AtomicReference clientFileDto = new AtomicReference<>(); var fileUri = file.getUri(); var fsPath = Paths.get(fileUri); SonarLanguage sqLanguage = AnalysisClientInputFile.toSqLanguage(file.getLanguageId().toLowerCase(Locale.ROOT)); @@ -103,14 +123,14 @@ public void notifyBackendWithFileLanguageAndContent(VersionedOpenFile file) { var relativePath = baseDir.relativize(fsPath); var folderUri = folder.getUri().toString(); var isTest = isTestFile(file, settings); - filesToNotify.add(new ClientFileDto(fileUri, relativePath, folderUri, isTest, StandardCharsets.UTF_8.name(), + clientFileDto.set(new ClientFileDto(fileUri, relativePath, folderUri, isTest, StandardCharsets.UTF_8.name(), fsPath, file.getContent(), sqLanguage != null ? Language.valueOf(sqLanguage.name()) : null, true)); }, () -> { var isTest = isTestFile(file, settingsManager.getCurrentDefaultFolderSettings()); - filesToNotify.add(new ClientFileDto(fileUri, fsPath, ROOT_CONFIGURATION_SCOPE, isTest, StandardCharsets.UTF_8.name(), + clientFileDto.set(new ClientFileDto(fileUri, fsPath, ROOT_CONFIGURATION_SCOPE, isTest, StandardCharsets.UTF_8.name(), fsPath, file.getContent(), sqLanguage != null ? Language.valueOf(sqLanguage.name()) : null, true)); }); - backendServiceFacade.getBackendService().updateFileSystem(List.of(), filesToNotify); + return clientFileDto.get(); } private boolean isTestFile(URI fileUri, WorkspaceFolderSettings settings) { diff --git a/src/test/java/org/sonarsource/sonarlint/ls/clientapi/SonarLintVSCodeClientTests.java b/src/test/java/org/sonarsource/sonarlint/ls/clientapi/SonarLintVSCodeClientTests.java index c8fe6ca32..ab7b0b130 100644 --- a/src/test/java/org/sonarsource/sonarlint/ls/clientapi/SonarLintVSCodeClientTests.java +++ b/src/test/java/org/sonarsource/sonarlint/ls/clientapi/SonarLintVSCodeClientTests.java @@ -602,7 +602,7 @@ void shouldForwardOpenIssueRequest() { var fileUri = fileInAWorkspaceFolderPath.toUri(); var textRangeDto = new TextRangeDto(1, 2, 3, 4); var issueDetailsDto = new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", FILE_PYTHON, "branch", "PR", "this is wrong", + "issueKey", FILE_PYTHON, "this is wrong", "29.09.2023", "print('ddd')", false, List.of()); var showIssueParams = new ShowIssueParams(fileUri.toString(), issueDetailsDto); @@ -625,7 +625,7 @@ void shouldForwardOpenIssueRequestWithoutRuleDescriptionWhenBindingDoesNotExist( var fileUri = fileInAWorkspaceFolderPath.toUri(); var textRangeDto = new TextRangeDto(1, 2, 3, 4); var issueDetailsDto = new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", FILE_PYTHON, "bb", null, "this is wrong", "29.09.2023", "print('ddd')", + "issueKey", FILE_PYTHON, "this is wrong", "29.09.2023", "print('ddd')", false, List.of()); when(bindingManager.getBindingIfExists(fileUri)) .thenReturn(Optional.empty()); @@ -643,7 +643,7 @@ void shouldForwardOpenIssueRequestWithRuleDescriptionWhenBindingDoesExist() { var fileUri = fileInAWorkspaceFolderPath.toUri(); var textRangeDto = new TextRangeDto(1, 2, 3, 4); var issueDetailsDto = new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", FILE_PYTHON, "bb", null, "this is wrong", "29.09.2023", "print('ddd')", + "issueKey", FILE_PYTHON, "this is wrong", "29.09.2023", "print('ddd')", false, List.of()); when(bindingManager.getBindingIfExists(fileUri)) .thenReturn(Optional.of(new ProjectBinding("connectionId", "projectKey"))); diff --git a/src/test/java/org/sonarsource/sonarlint/ls/commands/ShowAllLocationsCommandTests.java b/src/test/java/org/sonarsource/sonarlint/ls/commands/ShowAllLocationsCommandTests.java index 0d36b0dba..0815e4b9a 100644 --- a/src/test/java/org/sonarsource/sonarlint/ls/commands/ShowAllLocationsCommandTests.java +++ b/src/test/java/org/sonarsource/sonarlint/ls/commands/ShowAllLocationsCommandTests.java @@ -139,7 +139,7 @@ void shouldBuildCommandParamsFromShowIssueParams() { var textRangeDto = new TextRangeDto(1, 0, 1, 13); var showIssueParams = new ShowIssueParams(workspaceFolderPath.toUri().toString(), new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", Path.of("myFile.py"), "branch", "pr", "this is wrong", + "issueKey", Path.of("myFile.py"), "this is wrong", "29.09.2023", "print('1234')", false, flows)); var result = new ShowAllLocationsCommand.Param(showIssueParams, "connectionId", true); @@ -152,7 +152,7 @@ void shouldBuildCommandParamsFromShowIssueParams() { void shouldBuildCommandParamsFromShowIssueParamsForFileLevelIssue() { var textRangeDto = new TextRangeDto(0, 0, 0, 0); var showIssueParams = new ShowIssueParams(workspaceFolderPath.toUri().toString(), new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", Path.of("myFile.py"), "branch", null, "this is wrong", + "issueKey", Path.of("myFile.py"), "this is wrong", "29.09.2023", """ print('1234') print('aa') @@ -168,7 +168,7 @@ void shouldBuildCommandParamsFromShowIssueParamsForFileLevelIssue() { void shouldBuildCommandParamsFromShowIssueParamsForInvalidTextRange() { var textRangeDto = new TextRangeDto(-1, 0, -2, 0); var showIssueParams = new ShowIssueParams(workspaceFolderPath.toUri().toString(), new IssueDetailsDto(textRangeDto, "rule:S1234", - "issueKey", Path.of("myFile.py"), "bb", "1234", "this is wrong", + "issueKey", Path.of("myFile.py"), "this is wrong", "29.09.2023", "print('1234')", false, List.of())); var result = new ShowAllLocationsCommand.Param(showIssueParams, "connectionId", true); diff --git a/src/test/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessorTest.java b/src/test/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessorTest.java new file mode 100644 index 000000000..4918fbc18 --- /dev/null +++ b/src/test/java/org/sonarsource/sonarlint/ls/folders/ModuleEventsProcessorTest.java @@ -0,0 +1,87 @@ +/* + * 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.folders; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.sonarsource.sonarlint.core.rpc.protocol.common.Language; +import org.sonarsource.sonarlint.ls.backend.BackendServiceFacade; +import org.sonarsource.sonarlint.ls.file.FileTypeClassifier; +import org.sonarsource.sonarlint.ls.file.VersionedOpenFile; +import org.sonarsource.sonarlint.ls.java.JavaConfigCache; +import org.sonarsource.sonarlint.ls.settings.SettingsManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ModuleEventsProcessorTest { + ModuleEventsProcessor moduleEventsProcessor; + WorkspaceFoldersManager workspaceFoldersManager = mock(WorkspaceFoldersManager.class); + FileTypeClassifier fileTypeClassifier = mock(FileTypeClassifier.class); + JavaConfigCache javaConfigCache = mock(JavaConfigCache.class); + BackendServiceFacade backendServiceFacade = mock(BackendServiceFacade.class); + SettingsManager settingsManager = mock(SettingsManager.class); + + @BeforeEach + void setUp() { + moduleEventsProcessor = new ModuleEventsProcessor(workspaceFoldersManager, fileTypeClassifier, javaConfigCache, backendServiceFacade, settingsManager); + } + + @Test + void should_get_client_file_dto_outside_workspace_folder() { + var content = "print('Hello, World!')"; + var testFile1 = new VersionedOpenFile(URI.create("file:///tmp/test.py"), "python", 1, content); + + var clientFileDto = moduleEventsProcessor.getClientFileDto(testFile1); + + assertThat(clientFileDto).isNotNull(); + assertThat(clientFileDto.getDetectedLanguage()).isEqualTo(Language.PYTHON); + assertThat(clientFileDto.getContent()).isEqualTo(content); + assertThat(clientFileDto.getConfigScopeId()).isEqualTo(BackendServiceFacade.ROOT_CONFIGURATION_SCOPE); + assertThat(clientFileDto.getUri()).hasToString(testFile1.getUri().toString()); + assertThat(clientFileDto.getCharset()).isEqualTo(StandardCharsets.UTF_8.name()); + } + + @Test + void should_get_client_file_dto_inside_workspace_folder() { + var content = "print('Hello, World!')"; + var fileUri = URI.create("file:///tmp/test.py"); + var mockFolder = mock(WorkspaceFolderWrapper.class); + when(workspaceFoldersManager.findFolderForFile(fileUri)).thenReturn(Optional.of(mockFolder)); + when(mockFolder.getRootPath()).thenReturn(Path.of(URI.create("file:///tmp/").getPath())); + when(mockFolder.getUri()).thenReturn(URI.create("file:///tmp/")); + when(mockFolder.getSettings()).thenReturn(null); + var testFile1 = new VersionedOpenFile(fileUri, "python", 1, content); + + var clientFileDto = moduleEventsProcessor.getClientFileDto(testFile1); + + assertThat(clientFileDto).isNotNull(); + assertThat(clientFileDto.getDetectedLanguage()).isEqualTo(Language.PYTHON); + assertThat(clientFileDto.getContent()).isEqualTo(content); + assertThat(clientFileDto.getConfigScopeId()).isEqualTo("file:///tmp/"); + assertThat(clientFileDto.getUri()).hasToString(testFile1.getUri().toString()); + assertThat(clientFileDto.getCharset()).isEqualTo(StandardCharsets.UTF_8.name()); + } +} \ No newline at end of file