diff --git a/openai-coder/src/main/java/tech/picnic/errorprone/openai/OpenAi.java b/openai-coder/src/main/java/tech/picnic/errorprone/openai/OpenAi.java index 57c9cb3a..8a325547 100644 --- a/openai-coder/src/main/java/tech/picnic/errorprone/openai/OpenAi.java +++ b/openai-coder/src/main/java/tech/picnic/errorprone/openai/OpenAi.java @@ -3,6 +3,10 @@ package tech.picnic.errorprone.openai; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.theokanning.openai.completion.chat.ChatCompletionRequest; +import com.theokanning.openai.completion.chat.ChatCompletionResult; +import com.theokanning.openai.completion.chat.ChatMessage; import com.theokanning.openai.edit.EditRequest; import com.theokanning.openai.service.OpenAiService; import java.time.Duration; @@ -42,13 +46,30 @@ public class OpenAi implements AutoCloseable { .model("code-davinci-edit-001") .instruction(instruction) .temperature(0.0) - .n(1) .build()) .getChoices() .get(0) .getText(); } + // XXX: Improve error handling, including checking the finish reason. + String requestChatCompletion(String instruction) { + return openAiService + .createChatCompletion( + ChatCompletionRequest.builder() + .messages( + ImmutableList.of( + new ChatMessage("system", "You are an expert Java developer"), + new ChatMessage("user", instruction))) + .model("gpt-3.5-turbo") + .temperature(0.0) + .build()) + .getChoices() + .get(0) + .getMessage() + .getContent(); + } + @Override public void close() { openAiService.shutdownExecutor(); diff --git a/openai-coder/src/main/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractor.java b/openai-coder/src/main/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractor.java index 37bfe9f7..a0e77baa 100644 --- a/openai-coder/src/main/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractor.java +++ b/openai-coder/src/main/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractor.java @@ -14,9 +14,12 @@ import java.util.stream.Stream; * Compiler message format */ // XXX: Can path be relative? If so, that'd clash with `CheckStyleIssueExtractor`. +// ^ I think we should allow this, and then introduce a way to _chain_ extractors using short +// circuiting. // XXX: Also replace "Did you mean to remove" with "Remove"? // XXX: Also replace "Did you mean '" with "Instead use '"? // XXX: Concat "Did you mean" message to the preceding line? +// XXX: More generally join lines by `. ` (inserting dot if necessary)? // ^ For debuggability it could make sense to keep the original message, and _separately_ generate // the OpenAI prompt. final class PlexusCompilerIssueExtractor implements IssueExtractor { @@ -24,7 +27,7 @@ final class PlexusCompilerIssueExtractor implements IssueExtractor { // https://github.com/codehaus-plexus/plexus-compiler/blob/a5775b2258349b7c0d7c7759f162c80672328a0e/plexus-compiler-api/src/main/java/org/codehaus/plexus/compiler/CompilerMessage.java#L271-L296. private static final Pattern LOG_LINE_FORMAT = Pattern.compile( - "^(?/.+?\\.java):\\[(?\\d+)(?:,(?\\d+))?\\] (?.+)$", + "^(?/.+?\\.java):(?:\\[(?\\d+)(?:,(?\\d+))?\\])? (?.+)$", Pattern.DOTALL); private static final Pattern ERROR_PRONE_DOCUMENTATION_REFERENCE = Pattern.compile("^\\s*\\(see .+\\)\\s*$"); diff --git a/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlayGroundTest.java b/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlayGroundTest.java new file mode 100644 index 00000000..15f4cbac --- /dev/null +++ b/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlayGroundTest.java @@ -0,0 +1,72 @@ +package tech.picnic.errorprone.openai; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static tech.picnic.errorprone.openai.OpenAi.OPENAI_TOKEN_VARIABLE; + +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +// XXX: Drop this code. +@TestInstance(Lifecycle.PER_CLASS) +@EnabledIfSystemProperty(named = OPENAI_TOKEN_VARIABLE, matches = ".*") +final class PlayGroundTest { + private final OpenAi openAi = OpenAi.create(); + + @AfterAll + void tearDown() { + openAi.close(); + } + + @Test + @Disabled + void test() { + String input = + """ + I would like to generate Refaster rules that match a Java expression A and transform it into an equivalent Java expression B. + + This is an example of a Refaster rule that unwraps `Comparator#comparing` arguments to `Comparator#thenComparing`: + + import static java.util.Comparator.comparing; + import com.google.errorprone.refaster.annotation.AfterTemplate; + import com.google.errorprone.refaster.annotation.BeforeTemplate; + import java.util.Comparator; + import java.util.function.Function; + + /** Don't explicitly create {@link Comparator}s unnecessarily. */ + final class ThenComparing> { + @BeforeTemplate + Comparator before(Comparator cmp, Function function) { + return cmp.thenComparing(comparing(function)); + } + + @AfterTemplate + Comparator after(Comparator cmp, Function function) { + return cmp.thenComparing(function); + } + } + + ### + + Write a Refaster rule that transforms `mono.blockOptional().map(function)` into `mono.map(function).blockOptional()`. + + Requirements: + - Just write the new code. Don't explain yourself. + - If the rule requires type parameters, declare those on the class. + - Add all relevant imports. + """ + .stripTrailing(); + + // XXX: ^ That trailing whitespace removal is crucial!! + + assertThat(openAi.requestChatCompletion(input)).isEqualTo("XXX"); + } +} diff --git a/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractorTest.java b/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractorTest.java index b95333ea..64bb08fc 100644 --- a/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractorTest.java +++ b/openai-coder/src/test/java/tech/picnic/errorprone/openai/PlexusCompilerIssueExtractorTest.java @@ -14,8 +14,6 @@ import tech.picnic.errorprone.openai.IssueExtractor.Issue; final class PlexusCompilerIssueExtractorTest { private final IssueExtractor issueExtractor = new PlexusCompilerIssueExtractor(); - // XXX: Add column absent test case. - // XXX: Add line absent test case (and update the code to cover this case; see the Plexus code). private static Stream extractTestCases() { /* { input, expected } */ return Stream.of( @@ -36,6 +34,26 @@ final class PlexusCompilerIssueExtractorTest { OptionalInt.of(30), OptionalInt.of(22), "no comment"))), + arguments( + """ + /absolute/path/to/MyClass2.java:[123] error message without column specification + """, + ImmutableSet.of( + new Issue( + "/absolute/path/to/MyClass2.java", + OptionalInt.of(123), + OptionalInt.empty(), + "error message without column specification"))), + arguments( + """ + /absolute/path/to/MyClass3.java: error message without location specification + """, + ImmutableSet.of( + new Issue( + "/absolute/path/to/MyClass3.java", + OptionalInt.empty(), + OptionalInt.empty(), + "error message without location specification"))), arguments( """ /absolute/path/to/another/Class.java:[10,17] cannot find symbol