|
| 1 | +package org.approvaltests.inline; |
| 2 | + |
| 3 | +import com.spun.util.StringUtils; |
| 4 | +import com.spun.util.io.FileUtils; |
| 5 | +import org.approvaltests.core.ApprovalFailureReporter; |
| 6 | +import org.approvaltests.core.ApprovalReporterWithCleanUp; |
| 7 | +import org.approvaltests.namer.StackTraceNamer; |
| 8 | +import org.lambda.functions.Function2; |
| 9 | +import org.lambda.functions.Function3; |
| 10 | +import java.io.File; |
| 11 | + |
| 12 | +public class InlineKotlinReporter implements ApprovalFailureReporter, ApprovalReporterWithCleanUp { |
| 13 | + private final String sourceFilePath; |
| 14 | + private final StackTraceNamer stackTraceNamer; |
| 15 | + private final Function2<String, String, String> footerCreator; |
| 16 | + private final ApprovalFailureReporter reporter; |
| 17 | + private String additionalLines = null; |
| 18 | + private Function3<String, String, String, String> createNewReceivedFileText; |
| 19 | + |
| 20 | + public InlineKotlinReporter(ApprovalFailureReporter reporter, Function2<String, String, String> footerCreator) { |
| 21 | + this.reporter = reporter; |
| 22 | + this.stackTraceNamer = new StackTraceNamer(); |
| 23 | + this.sourceFilePath = stackTraceNamer.getSourceFilePath(); |
| 24 | + this.createNewReceivedFileText = (kotlinSourceCode, actual, methodName) -> |
| 25 | + createNewReceivedFileText(kotlinSourceCode, actual, methodName); |
| 26 | + this.footerCreator = footerCreator != null ? footerCreator : (source, actual) -> ""; |
| 27 | + } |
| 28 | + |
| 29 | + public InlineKotlinReporter(ApprovalFailureReporter reporter) { |
| 30 | + this(reporter, null); |
| 31 | + } |
| 32 | + |
| 33 | + @Override |
| 34 | + public boolean report(String received, String approved) { |
| 35 | + additionalLines = footerCreator.call(received, approved); |
| 36 | + String sourceFile = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt"; |
| 37 | + String newSource = createReceived(FileUtils.readFile(received)); |
| 38 | + return reporter.report(newSource, sourceFile); |
| 39 | + } |
| 40 | + |
| 41 | + public String createReceived(String actual) { |
| 42 | + String file = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt"; |
| 43 | + String received = getReceivedFileName(); |
| 44 | + String text = FileUtils.readFile(file); |
| 45 | + String fullText = this.createNewReceivedFileText.call( |
| 46 | + text, actual + additionalLines, |
| 47 | + this.stackTraceNamer.getInfo().getMethodName() |
| 48 | + ); |
| 49 | + FileUtils.writeFile(new File(received), fullText); |
| 50 | + return received; |
| 51 | + } |
| 52 | + |
| 53 | + private String getReceivedFileName() { |
| 54 | + return sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".received.txt"; |
| 55 | + } |
| 56 | + |
| 57 | + @Override |
| 58 | + public void cleanUp(String received, String approved) { |
| 59 | + FileUtils.delete(getReceivedFileName()); |
| 60 | + } |
| 61 | + |
| 62 | + public static String createNewReceivedFileText(String kotlinSourceCode, String actual, String methodName) { |
| 63 | + String normalizedSourceCode = kotlinSourceCode.replaceAll("\\r\\n", "\n"); |
| 64 | + CodeParts codeParts = CodeParts.splitCode(normalizedSourceCode, methodName); |
| 65 | + if (codeParts.method != null && codeParts.method.contains("expected = \"\"\"")) { |
| 66 | + replaceExpected(codeParts, actual); |
| 67 | + } else { |
| 68 | + addExpected(codeParts, actual); |
| 69 | + } |
| 70 | + return codeParts.getFullCode(); |
| 71 | + } |
| 72 | + |
| 73 | + private static void addExpected(CodeParts codeParts, String actual) { |
| 74 | + int start = codeParts.method.indexOf("{") + 2; |
| 75 | + String before = codeParts.method.substring(0, start); |
| 76 | + String after = codeParts.method.substring(start); |
| 77 | + codeParts.method = before + getExpected(actual, codeParts.tab) + after; |
| 78 | + } |
| 79 | + |
| 80 | + private static String getExpected(String actual, String tab) { |
| 81 | + return String.format( |
| 82 | + "%s%sval expected = \"\"\"\n%s%s%s%s\"\"\".trimIndent()\n", tab, tab, indent(actual, tab), tab, tab, |
| 83 | + tab |
| 84 | + ); |
| 85 | + } |
| 86 | + |
| 87 | + private static void replaceExpected(CodeParts codeParts, String actual) { |
| 88 | + int start = codeParts.method.indexOf("expected = \"\"\""); |
| 89 | + start = codeParts.method.substring(0, start).lastIndexOf("\n") + 1; |
| 90 | + // Find the closing """ by looking for it at the start of a line (after whitespace) |
| 91 | + int searchPos = start + "expected = \"\"\"".length(); |
| 92 | + int end = -1; |
| 93 | + while (searchPos < codeParts.method.length()) { |
| 94 | + int nextTripleQuote = codeParts.method.indexOf("\"\"\"", searchPos); |
| 95 | + if (nextTripleQuote == -1) break; |
| 96 | + // Check if this """ is at the start of a line (preceded only by whitespace) |
| 97 | + int lineStart = codeParts.method.lastIndexOf("\n", nextTripleQuote - 1) + 1; |
| 98 | + String textBeforeQuote = codeParts.method.substring(lineStart, nextTripleQuote); |
| 99 | + if (textBeforeQuote.trim().isEmpty()) { |
| 100 | + end = nextTripleQuote; |
| 101 | + break; |
| 102 | + } |
| 103 | + searchPos = nextTripleQuote + 1; |
| 104 | + } |
| 105 | + if (end == -1) { |
| 106 | + // Fallback to old behavior if we can't find it |
| 107 | + end = codeParts.method.indexOf("\"\"\"", start + "expected = \"\"\"".length()); |
| 108 | + } |
| 109 | + end += 3; // Move past the closing """ |
| 110 | + // Check if there's a .trimIndent() after the closing """ |
| 111 | + String afterTripleQuote = codeParts.method.substring(end); |
| 112 | + if (afterTripleQuote.startsWith(".trimIndent()")) { |
| 113 | + end += ".trimIndent()".length(); |
| 114 | + } |
| 115 | + end = codeParts.method.indexOf("\n", end) + 1; |
| 116 | + String before = codeParts.method.substring(0, start); |
| 117 | + String after = codeParts.method.substring(end); |
| 118 | + codeParts.method = before + getExpected(actual, codeParts.tab) + after; |
| 119 | + } |
| 120 | + |
| 121 | + public static String indent(String actual, String tab) { |
| 122 | + String[] split = StringUtils.split(actual, "\n"); |
| 123 | + String output = ""; |
| 124 | + for (String line : split) { |
| 125 | + output += tab + tab + tab + line + "\n"; |
| 126 | + } |
| 127 | + return output; |
| 128 | + } |
| 129 | +} |
0 commit comments