Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
""");

Expand All @@ -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<String, InputSchemaPropertyValue> properties = new HashMap<>();
properties.put(FILE_PATHS, filePathsProperty);

Expand Down Expand Up @@ -160,7 +162,7 @@ private List<String> 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.
Expand All @@ -179,11 +181,7 @@ public GetErrorsResult getErrors(List<String> 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;
Expand All @@ -193,4 +191,42 @@ public GetErrorsResult getErrors(List<String> 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";
};
}

}