This commit is contained in:
Stephan Schroevers
2023-04-17 22:31:28 +02:00
parent 68f6c328b3
commit e220f0e65c
8 changed files with 188 additions and 73 deletions

View File

@@ -0,0 +1,18 @@
package tech.picnic.errorprone.openai;
import com.google.common.collect.ImmutableSet;
import java.util.stream.Stream;
// XXX: Use this class, or drop it.
final class AggregatingIssueExtractor implements IssueExtractor {
private final ImmutableSet<IssueExtractor> delegates;
AggregatingIssueExtractor(ImmutableSet<IssueExtractor> delegates) {
this.delegates = delegates;
}
@Override
public Stream<Issue> extract(String str) {
return delegates.stream().flatMap(delegate -> delegate.extract(str));
}
}

View File

@@ -1,6 +1,7 @@
package tech.picnic.errorprone.openai;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -11,11 +12,13 @@ import java.util.Map;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.openai.MavenLogParser.JavacAndCheckstyleLogLineAnalyzer;
import tech.picnic.errorprone.openai.MavenLogParser.LogLineAnalyzer;
// XXX: Consider using https://picocli.info/quick-guide.html. Can also be used for an interactive
// CLI.
// XXX: Introduce README.
// XXX: Consider creating a binary executable using GraalVM.
// XXX: Add support for sending a suitable subset of the code to OpenAI, so as (a) to better deal
// with the token limit and (b) potentially reduce cost.
public final class AiPatcher {
private static final Pattern FILE_LOCATION_MARKER =
Pattern.compile("^(.*?\\.java):\\[(\\d+)(?:,(\\d+))?\\] ");
@@ -91,17 +94,20 @@ public final class AiPatcher {
// XXX: Clean this up.
private static void extractPathAndMessage(String logLine, BiConsumer<Path, String> sink) {
LogLineAnalyzer analyzer =
new JavacAndCheckstyleLogLineAnalyzer(
new PathFinder(FileSystems.getDefault(), Path.of("")));
IssueExtractor analyzer =
new AggregatingIssueExtractor(
ImmutableSet.of(new JavacIssueExtractor(), new CheckstyleIssueExtractor()));
analyzer
.analyze(logLine)
.extract(logLine)
.findFirst()
.ifPresent(
issue ->
sink.accept(
issue.file(),
// XXX: Fix.
new PathFinder(FileSystems.getDefault(), Path.of(""))
.findPath(issue.file())
.orElseThrow(),
issue.column().isEmpty()
? String.format("- Line %s: %s", issue.line(), issue.message())
: String.format(

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.openai;
import java.util.regex.Pattern;
import java.util.stream.Stream;
// [ERROR] src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompiler.java:[13,1]
// (annotation) AnnotationUseStyle: Annotation style must be 'COMPACT_NO_ARRAY'.
// [ERROR] src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompiler.java:[14,15]
// (annotation) AnnotationUseStyle: Annotation array values cannot contain trailing comma.
// [ERROR] src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompiler.java:[16]
// (regexp) RegexpMultiline: Avoid blank lines at the start of a block.
final class CheckstyleIssueExtractor implements IssueExtractor {
private static final Pattern LOG_LINE_FORMAT =
Pattern.compile(
"^(?<file>.+?\\.java):\\[(?<line>\\d+)(?:,(?<column>\\d+))?\\] \\(.+?\\) .+: (?<message>.+)$",
Pattern.DOTALL);
private final IssueExtractor delegate = new RegexIssueExtractor(LOG_LINE_FORMAT);
@Override
public Stream<Issue> extract(String str) {
return delegate.extract(str);
}
}

View File

@@ -0,0 +1,18 @@
package tech.picnic.errorprone.openai;
import com.google.common.collect.ImmutableList;
// XXX: Use or drop.
final class FileEditSuggester {
private final OpenAi openAi;
FileEditSuggester(OpenAi openAi) {
this.openAi = openAi;
}
ImmutableList<EditSuggestion> suggestEdits(String fileContent) {
return ImmutableList.of();
}
record EditSuggestion(String replacement, String unifiedPatch) {}
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.openai;
import java.util.OptionalInt;
import java.util.stream.Stream;
// XXX: Document
interface IssueExtractor {
Stream<Issue> extract(String str);
// XXX: Make `Path` a `String` and do path lookup post collection? (This would simplify things,
// but may close off some future possibilities.)
// ^ Not really. Where it matters we can double-resolve.
// XXX: ^ Also simplifies testing.
// XXX: Or move to separate file?
record Issue(String message, String file, int line, OptionalInt column) {
Issue withMessage(String message) {
return new Issue(message, file, line, column);
}
}
}

View File

@@ -0,0 +1,63 @@
package tech.picnic.errorprone.openai;
import static java.util.stream.Collectors.joining;
import java.util.regex.Pattern;
import java.util.stream.Stream;
// [WARNING]
// /home/sschroevers/workspace/picnic/error-prone-support/openai-coder/src/main/java/tech/picnic/errorprone/openai/AiPatcher.java:[30,22] no comment
// [WARNING]
// /home/sschroevers/workspace/picnic/error-prone-support/openai-coder/src/main/java/tech/picnic/errorprone/openai/AiPatcher.java:[22,32] [UnusedVariable] The field 'FILE_LOCATION_MARKER' is never read.
// (see https://errorprone.info/bugpattern/UnusedVariable)
// Did you mean to remove this line or 'static {
// Pattern.compile("^(.*?\\.java):\\[(\\d+)(?:,(\\d+))?\\] "); }'?
// XXX: Create another `LogLineAnalyzer` for Error Prone test compiler output.
// XXX: Also replace "Did you mean to remove" with "Remove"?
final class JavacIssueExtractor implements IssueExtractor {
private static final Pattern LOG_LINE_FORMAT =
Pattern.compile(
"^(?<file>.+?\\.java):\\[(?<line>\\d+)(?:,(?<column>\\d+))?\\] (?<message>.+)$",
Pattern.DOTALL);
private static final Pattern ERROR_PRONE_DOCUMENTATION_REFERENCE =
Pattern.compile("^\\s*\\(see .+\\)\\s+$");
private final IssueExtractor delegate = new RegexIssueExtractor(LOG_LINE_FORMAT);
@Override
public Stream<Issue> extract(String str) {
return delegate
.extract(str)
.map(issue -> issue.withMessage(removeErrorProneDocumentationReference(issue.message())));
}
private static String removeErrorProneDocumentationReference(String message) {
return message
.lines()
.filter(line -> !ERROR_PRONE_DOCUMENTATION_REFERENCE.matcher(line).matches())
.collect(joining(""));
}
// // XXX: drop the `(see` line.
// @Override
// public Stream<Issue> extract(String str) {
// Matcher matcher = FILE_LOCATION_MARKER.matcher(str);
// if (!matcher.find()) {
// return Stream.empty();
// }
//
// return pathFinder
// .findPath(matcher.group("file"))
// .map(
// p ->
// new Issue(
// str.substring(matcher.end()),
// p,
// Integer.parseInt(matcher.group("line")),
// matcher.group("column") == null
// ? OptionalInt.empty()
// : OptionalInt.of(Integer.parseInt(matcher.group("column")))))
// .stream();
// }
}

View File

@@ -1,66 +0,0 @@
package tech.picnic.errorprone.openai;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.OptionalInt;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
final class MavenLogParser {
private final ImmutableSet<LogLineAnalyzer> logLineAnalyzers;
// XXX: Use.
MavenLogParser(ImmutableSet<LogLineAnalyzer> logLineAnalyzers) {
this.logLineAnalyzers = logLineAnalyzers;
}
////////////////////////////////////////////////////
// XXX: Create a second `LogLineAnalyzer` for Error Prone test compiler output.
// XXX: Review class name.
// XXX: Provide additional implementations.
static final class JavacAndCheckstyleLogLineAnalyzer implements LogLineAnalyzer {
private static final Pattern FILE_LOCATION_MARKER =
Pattern.compile("^(.*?\\.java):\\[(\\d+)(?:,(\\d+))?\\] ");
private final PathFinder pathFinder;
JavacAndCheckstyleLogLineAnalyzer(PathFinder pathFinder) {
this.pathFinder = pathFinder;
}
@Override
public Stream<Issue> analyze(String logLine) {
Matcher matcher = FILE_LOCATION_MARKER.matcher(logLine);
if (!matcher.find()) {
return Stream.empty();
}
return pathFinder
.findPath(matcher.group(1))
.map(
p ->
new Issue(
logLine.substring(matcher.end()),
p,
Integer.parseInt(matcher.group(2)),
matcher.group(3) == null
? OptionalInt.empty()
: OptionalInt.of(Integer.parseInt(matcher.group(3)))))
.stream();
}
}
// XXX: Move to separate file.
interface LogLineAnalyzer {
Stream<Issue> analyze(String line);
}
// XXX: Make `Path` a `String` and do path lookup post collection? (This would simplify things,
// but may close off some future possibilities.)
// ^ Not really. Where it matters we can double-resolve.
// XXX: Move to separate file, or inside `LogLineAnalyzer`.
record Issue(String message, Path file, int line, OptionalInt column) {}
}

View File

@@ -0,0 +1,30 @@
package tech.picnic.errorprone.openai;
import java.util.OptionalInt;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
// XXX: Document contract
final class RegexIssueExtractor implements IssueExtractor {
private final Pattern pattern;
RegexIssueExtractor(Pattern pattern) {
this.pattern = pattern;
}
@Override
public Stream<Issue> extract(String str) {
return Stream.of(pattern.matcher(str))
.filter(Matcher::matches)
.map(
matcher ->
new IssueExtractor.Issue(
matcher.group("message"),
matcher.group("file"),
Integer.parseInt(matcher.group("line")),
matcher.group("column") == null
? OptionalInt.empty()
: OptionalInt.of(Integer.parseInt(matcher.group("column")))));
}
}