xref: /aosp_15_r20/external/ktfmt/core/src/test/java/com/facebook/ktfmt/testutil/KtfmtTruth.kt (revision 5be3f65c8cf0e6db0a7e312df5006e8e93cdf9ec)
1 /*
<lambda>null2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.facebook.ktfmt.testutil
18 
19 import com.facebook.ktfmt.debughelpers.PrintAstVisitor
20 import com.facebook.ktfmt.format.Formatter
21 import com.facebook.ktfmt.format.FormattingOptions
22 import com.facebook.ktfmt.format.Parser
23 import com.google.common.truth.FailureMetadata
24 import com.google.common.truth.Subject
25 import com.google.common.truth.Truth
26 import org.intellij.lang.annotations.Language
27 import org.junit.Assert
28 
29 var defaultTestFormattingOptions: FormattingOptions = Formatter.META_FORMAT
30 
31 /**
32  * Verifies the given code passes through formatting, and stays the same at the end
33  *
34  * @param code a code string that contains an optional first line made of at least 8 '-' or '/' in
35  *   the case [deduceMaxWidth] is true. For example:
36  * ```
37  * ////////////////////////
38  * // exactly 24 `/` above
39  * // and that will be the
40  * // size of the line
41  * fun f()
42  * ```
43  *
44  * @param deduceMaxWidth if this is true the code string should start with a line of "-----" in the
45  *   beginning to indicate the max width to format by
46  */
47 fun assertFormatted(
48     @Language("kts") code: String,
49     formattingOptions: FormattingOptions = defaultTestFormattingOptions,
50     deduceMaxWidth: Boolean = false,
51 ) {
52   val first = code.lines().first()
53   var deducedCode = code
54   var maxWidth = FormattingOptions.DEFAULT_MAX_WIDTH
55   val lineWidthMarkers = setOf('-', '/')
56   val isFirstLineAMaxWidthMarker = first.length >= 8 && first.all { it in lineWidthMarkers }
57   if (deduceMaxWidth) {
58     if (!isFirstLineAMaxWidthMarker) {
59       throw RuntimeException(
60           "When deduceMaxWidth is true the first line needs to be all dashes only (i.e. ---)")
61     }
62     deducedCode = code.substring(code.indexOf('\n') + 1)
63     maxWidth = first.length
64   } else {
65     if (isFirstLineAMaxWidthMarker) {
66       throw RuntimeException(
67           "deduceMaxWidth is false, please remove the first dashes only line from the code (i.e. ---)")
68     }
69   }
70   assertThatFormatting(deducedCode)
71       .withOptions(formattingOptions.copy(maxWidth = maxWidth))
72       .isEqualTo(deducedCode)
73 }
74 
assertThatFormattingnull75 fun assertThatFormatting(@Language("kts") code: String): FormattedCodeSubject {
76   fun codes(): Subject.Factory<FormattedCodeSubject, String> {
77     return Subject.Factory { metadata, subject ->
78       FormattedCodeSubject(metadata, checkNotNull(subject))
79     }
80   }
81   return Truth.assertAbout(codes()).that(code)
82 }
83 
84 class FormattedCodeSubject(metadata: FailureMetadata, private val code: String) :
85     Subject(metadata, code) {
86   private var options: FormattingOptions = defaultTestFormattingOptions
87   private var allowTrailingWhitespace = false
88 
withOptionsnull89   fun withOptions(options: FormattingOptions): FormattedCodeSubject {
90     this.options = options
91     return this
92   }
93 
allowTrailingWhitespacenull94   fun allowTrailingWhitespace(): FormattedCodeSubject {
95     this.allowTrailingWhitespace = true
96     return this
97   }
98 
isEqualTonull99   fun isEqualTo(@Language("kts") expectedFormatting: String) {
100     if (!allowTrailingWhitespace && expectedFormatting.lines().any { it.endsWith(" ") }) {
101       throw RuntimeException(
102           "Expected code contains trailing whitespace, which the formatter usually doesn't output:\n" +
103               expectedFormatting
104                   .lines()
105                   .map { if (it.endsWith(" ")) "[$it]" else it }
106                   .joinToString("\n"))
107     }
108     val actualFormatting: String
109     try {
110       actualFormatting = Formatter.format(options, code)
111       if (actualFormatting != expectedFormatting) {
112         reportError(code)
113         println("# Output: ")
114         println("#".repeat(20))
115         println(actualFormatting)
116         println("# Expected: ")
117         println("#".repeat(20))
118         println(expectedFormatting)
119         println("#".repeat(20))
120         println(
121             "Need more information about the break operations? " +
122                 "Run test with assertion with \"FormattingOptions(debuggingPrintOpsAfterFormatting = true)\"")
123       }
124     } catch (e: Error) {
125       reportError(code)
126       throw e
127     }
128     Assert.assertEquals(expectedFormatting, actualFormatting)
129   }
130 
reportErrornull131   private fun reportError(code: String) {
132     val file = Parser.parse(code)
133     println("# Parse tree of input: ")
134     println("#".repeat(20))
135     file.accept(PrintAstVisitor())
136     println()
137     println("# Input: ")
138     println("#".repeat(20))
139     println(code)
140     println()
141   }
142 }
143