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