diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsToolTests.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsToolTests.java index e13ef1de7..78b94e327 100644 --- a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsToolTests.java +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsToolTests.java @@ -6,11 +6,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.eclipse.core.resources.IMarker; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -92,4 +95,50 @@ void testGetErrorsWithInvalidFilePath() { assertTrue(result.content().contains("Resource not found for filePath: invalidPath1")); assertTrue(result.content().contains("Resource not found for filePath: invalidPath2")); } + + @Test + void testFormatProblemsListsAllSeveritiesErrorsFirst() { + IMarker[] markers = { + mockMarker(IMarker.SEVERITY_INFO, 3, "Info message"), + mockMarker(IMarker.SEVERITY_WARNING, 20, "Later warning"), + mockMarker(IMarker.SEVERITY_ERROR, 7, "Error message"), + mockMarker(IMarker.SEVERITY_WARNING, 5, "Earlier warning") }; + + String result = GetErrorsTool.formatProblems("/project/A.java", markers); + + assertEquals(""" + Problems in /project/A.java: + ERROR at line 7: Error message + WARNING at line 5: Earlier warning + WARNING at line 20: Later warning + INFO at line 3: Info message + """, result); + } + + @Test + void testFormatProblemsWithoutLineNumber() { + IMarker[] markers = { mockMarker(IMarker.SEVERITY_ERROR, -1, "Build path is incomplete") }; + + String result = GetErrorsTool.formatProblems("/project/A.java", markers); + + assertEquals(""" + Problems in /project/A.java: + ERROR: Build path is incomplete + """, result); + } + + @Test + void testFormatProblemsWithoutMarkers() { + String result = GetErrorsTool.formatProblems("/project/A.java", new IMarker[0]); + + assertEquals("No problems found in /project/A.java\n", result); + } + + private static IMarker mockMarker(int severity, int lineNumber, String message) { + IMarker marker = mock(IMarker.class); + when(marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO)).thenReturn(severity); + when(marker.getAttribute(IMarker.LINE_NUMBER, -1)).thenReturn(lineNumber); + when(marker.getAttribute(IMarker.MESSAGE, "")).thenReturn(message); + return marker; + } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsTool.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsTool.java index 63250eb9e..f552e2f29 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsTool.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/tools/GetErrorsTool.java @@ -4,6 +4,8 @@ package com.microsoft.copilot.eclipse.ui.chat.tools; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,9 +51,9 @@ public LanguageModelToolInformation getToolInformation() { // Set the name and description of the tool toolInfo.setName(TOOL_NAME); toolInfo.setDescription(""" - Get any compile or lint errors in a code file. - If the user mentions errors or problems in a file, they may be referring to these. - Use the tool to see the same errors that the user is seeing. + Get the compile and lint problems (errors, warnings and infos) of code files, as shown in the Problems view. + If the user mentions errors, warnings or problems in a file, they may be referring to these. + Use the tool to see the same problems that the user is seeing. Also use this tool after editing a file to validate the change. """); @@ -61,9 +63,9 @@ public LanguageModelToolInformation getToolInformation() { // Define the properties of the input schema InputSchemaPropertyValue filePathsProperty = new InputSchemaPropertyValue("array", - "Array of absolute file paths to check for errors"); + "Array of absolute file paths to check for problems"); filePathsProperty.setItems(new InputSchemaPropertyValue("string", - "The absolute path of a file to check for compile/lint errors")); + "The absolute path of a file to check for compile/lint problems")); Map properties = new HashMap<>(); properties.put(FILE_PATHS, filePathsProperty); @@ -160,7 +162,7 @@ private List parseMalformedJsonArray(String jsonStr) { } /** - * Retrieves errors from the Problems view for the given file URIs. + * Retrieves the problems shown in the Problems view for the given file paths. * * @param filePaths The list of file paths to check for errors. * @return A GetErrorsResult containing the errors found and whether any exceptions occurred. @@ -179,11 +181,7 @@ public GetErrorsResult getErrors(List filePaths) { } IMarker[] markers = file.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_ZERO); - for (IMarker marker : markers) { - if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) == IMarker.SEVERITY_ERROR) { - toolResult.append(marker.toString()).append(StringUtils.LF); - } - } + toolResult.append(formatProblems(filePath, markers)); } catch (CoreException e) { toolResult.append("Failed on file: ").append(filePath).append(e.getMessage()).append(StringUtils.LF); hasException = true; @@ -193,4 +191,42 @@ public GetErrorsResult getErrors(List filePaths) { return new GetErrorsResult(toolResult.toString(), hasException); } + /** + * Formats the problem markers of a file, errors first, then warnings and infos, each ordered by line. + */ + static String formatProblems(String filePath, IMarker[] markers) { + if (markers.length == 0) { + return "No problems found in " + filePath + StringUtils.LF; + } + StringBuilder result = new StringBuilder("Problems in ").append(filePath).append(':').append(StringUtils.LF); + Arrays.stream(markers) + .sorted(Comparator.comparingInt(GetErrorsTool::getSeverity).reversed() + .thenComparingInt(GetErrorsTool::getLineNumber)) + .forEach(marker -> { + result.append(getSeverityLabel(getSeverity(marker))); + int lineNumber = getLineNumber(marker); + if (lineNumber > 0) { + result.append(" at line ").append(lineNumber); + } + result.append(": ").append(marker.getAttribute(IMarker.MESSAGE, StringUtils.EMPTY)).append(StringUtils.LF); + }); + return result.toString(); + } + + private static int getSeverity(IMarker marker) { + return marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); + } + + private static int getLineNumber(IMarker marker) { + return marker.getAttribute(IMarker.LINE_NUMBER, -1); + } + + private static String getSeverityLabel(int severity) { + return switch (severity) { + case IMarker.SEVERITY_ERROR -> "ERROR"; + case IMarker.SEVERITY_WARNING -> "WARNING"; + default -> "INFO"; + }; + } + } \ No newline at end of file