• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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