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 /**
30 * Verifies the given code passes through formatting, and stays the same at the end
31 *
32 * @param code a code string that contains an optional first line made of at least 8 '-' or '/' in
33 * the case [deduceMaxWidth] is true. For example:
34 * ```
35 * ////////////////////////
36 * // exactly 24 `/` above
37 * // and that will be the
38 * // size of the line
39 * fun f()
40 * ```
41 *
42 * @param deduceMaxWidth if this is true the code string should start with a line of "-----" in the
43 * beginning to indicate the max width to format by
44 */
45 fun assertFormatted(
46 @Language("kts") code: String,
47 formattingOptions: FormattingOptions = FormattingOptions(),
48 deduceMaxWidth: Boolean = false,
49 ) {
50 val first = code.lines().first()
51 var deducedCode = code
52 var maxWidth = FormattingOptions.DEFAULT_MAX_WIDTH
53 val lineWidthMarkers = setOf('-', '/')
54 val isFirstLineAMaxWidthMarker = first.length >= 8 && first.all { it in lineWidthMarkers }
55 if (deduceMaxWidth) {
56 if (!isFirstLineAMaxWidthMarker) {
57 throw RuntimeException(
58 "When deduceMaxWidth is true the first line needs to be all dashes only (i.e. ---)")
59 }
60 deducedCode = code.substring(code.indexOf('\n') + 1)
61 maxWidth = first.length
62 } else {
63 if (isFirstLineAMaxWidthMarker) {
64 throw RuntimeException(
65 "deduceMaxWidth is false, please remove the first dashes only line from the code (i.e. ---)")
66 }
67 }
68 assertThatFormatting(deducedCode)
69 .withOptions(formattingOptions.copy(maxWidth = maxWidth))
70 .isEqualTo(deducedCode)
71 }
72
assertThatFormattingnull73 fun assertThatFormatting(@Language("kts") code: String): FormattedCodeSubject {
74 fun codes(): Subject.Factory<FormattedCodeSubject, String> {
75 return Subject.Factory { metadata, subject ->
76 FormattedCodeSubject(metadata, checkNotNull(subject))
77 }
78 }
79 return Truth.assertAbout(codes()).that(code)
80 }
81
82 class FormattedCodeSubject(metadata: FailureMetadata, private val code: String) :
83 Subject(metadata, code) {
84 private var options: FormattingOptions = FormattingOptions()
85 private var allowTrailingWhitespace = false
86
withOptionsnull87 fun withOptions(options: FormattingOptions): FormattedCodeSubject {
88 this.options = options
89 return this
90 }
91
allowTrailingWhitespacenull92 fun allowTrailingWhitespace(): FormattedCodeSubject {
93 this.allowTrailingWhitespace = true
94 return this
95 }
96
isEqualTonull97 fun isEqualTo(@Language("kts") expectedFormatting: String) {
98 if (!allowTrailingWhitespace && expectedFormatting.lines().any { it.endsWith(" ") }) {
99 throw RuntimeException(
100 "Expected code contains trailing whitespace, which the formatter usually doesn't output:\n" +
101 expectedFormatting
102 .lines()
103 .map { if (it.endsWith(" ")) "[$it]" else it }
104 .joinToString("\n"))
105 }
106 val actualFormatting: String
107 try {
108 actualFormatting = Formatter.format(options, code)
109 if (actualFormatting != expectedFormatting) {
110 reportError(code)
111 println("# Output: ")
112 println("#".repeat(20))
113 println(actualFormatting)
114 println("# Expected: ")
115 println("#".repeat(20))
116 println(expectedFormatting)
117 println("#".repeat(20))
118 println(
119 "Need more information about the break operations? " +
120 "Run test with assertion with \"FormattingOptions(debuggingPrintOpsAfterFormatting = true)\"")
121 }
122 } catch (e: Error) {
123 reportError(code)
124 throw e
125 }
126 Assert.assertEquals(expectedFormatting, actualFormatting)
127 }
128
reportErrornull129 private fun reportError(code: String) {
130 val file = Parser.parse(code)
131 println("# Parse tree of input: ")
132 println("#".repeat(20))
133 file.accept(PrintAstVisitor())
134 println()
135 println("# Input: ")
136 println("#".repeat(20))
137 println(code)
138 println()
139 }
140 }
141