@@ -4,9 +4,11 @@ import com.squareup.cash.grammar.KotlinLexer
44import com.squareup.cash.grammar.KotlinParser.KotlinFileContext
55import com.squareup.cash.grammar.KotlinParser.ScriptContext
66import com.squareup.cash.grammar.KotlinParser.SemiContext
7+ import com.squareup.cash.grammar.KotlinParser.SemisContext
78import org.antlr.v4.runtime.CommonTokenStream
89import org.antlr.v4.runtime.ParserRuleContext
910import org.antlr.v4.runtime.Token
11+ import org.antlr.v4.runtime.tree.ParseTree
1012
1113/* *
1214 * Utilities for working with whitespace, including newlines, carriage returns, etc.
@@ -119,49 +121,67 @@ public object Whitespace {
119121 * Use this in conjunction with [trimGently] to maintain original end-of-file formatting.
120122 */
121123 public fun countTerminalNewlines (ctx : ScriptContext , tokens : CommonTokenStream ): Int {
122- return countTerminalNewlines(ctx as ParserRuleContext , tokens)
124+ return ctx.children
125+ // Start iterating from EOF
126+ .reversed()
127+ .asSequence()
128+ // Drop `EOF` (every file must have this)
129+ .drop(1 )
130+ // Take only the "semis", which is a semi-colon or a newline, followed by 0 or more newlines
131+ .takeWhile { parseTree ->
132+ parseTree.javaClass == SemiContext ::class .java && hasNoComments(parseTree, tokens)
133+ }
134+ // Note this is a `SemiContext` (singular!)
135+ .filterIsInstance<SemiContext >()
136+ // This is the "a newline, followed by 0 or more newlines"
137+ .flatMap { it.NL () }
138+ .count()
123139 }
124140
125141 /* *
126142 * Use this in conjunction with [trimGently] to maintain original end-of-file formatting.
127143 */
128144 public fun countTerminalNewlines (ctx : KotlinFileContext , tokens : CommonTokenStream ): Int {
129- return countTerminalNewlines(ctx as ParserRuleContext , tokens)
130- }
131-
132- private fun countTerminalNewlines (ctx : ParserRuleContext , tokens : CommonTokenStream ): Int {
133145 return ctx.children
134146 // Start iterating from EOF
135147 .reversed()
136148 .asSequence()
137149 // Drop `EOF` (every file must have this)
138150 .drop(1 )
151+ .filterIsInstance<ParserRuleContext >()
152+ // We need to reverse the order of the children too. If a node doesn't have children, use it.
153+ .flatMap { it.children?.reversed() ? : listOf (it) }
139154 // Take only the "semis", which is a semi-colon or a newline, followed by 0 or more newlines
140155 .takeWhile { parseTree ->
141- // Because comments are not part of the parse tree (they are shunted to a "hidden" channel),
142- // we need to check for them. Otherwise, we'll "detect" too many newlines at the end of a
143- // file, when that file has only comments and newlines at the end.
144- val hasNoComments = if (parseTree is ParserRuleContext ) {
145- val toLeft = parseTree.stop?.let { stop ->
146- tokens.getHiddenTokensToLeft(stop.tokenIndex).orEmpty().isEmpty()
147- } ? : true
148- val toRight = parseTree.stop?.let { stop ->
149- tokens.getHiddenTokensToRight(stop.tokenIndex).orEmpty().isEmpty()
150- } ? : true
151-
152- toLeft && toRight
153- } else {
154- true
155- }
156-
157- parseTree.javaClass == SemiContext ::class .java && hasNoComments
156+ parseTree.javaClass == SemisContext ::class .java && hasNoComments(parseTree, tokens)
158157 }
159- .filterIsInstance<SemiContext >()
158+ // Note this is a `SemisContext` (plural!)
159+ .filterIsInstance<SemisContext >()
160160 // This is the "a newline, followed by 0 or more newlines"
161161 .flatMap { it.NL () }
162162 .count()
163163 }
164164
165+ /* *
166+ * Because comments are not part of the parse tree (they are shunted to a "hidden" channel),
167+ * we need to check for them. Otherwise, we'll "detect" too many newlines at the end of a
168+ * file, when that file has only comments and newlines at the end.
169+ */
170+ private fun hasNoComments (parseTree : ParseTree , tokens : CommonTokenStream ): Boolean {
171+ return if (parseTree is ParserRuleContext ) {
172+ val toLeft = parseTree.stop?.let { stop ->
173+ tokens.getHiddenTokensToLeft(stop.tokenIndex).orEmpty().isEmpty()
174+ } ? : true
175+ val toRight = parseTree.stop?.let { stop ->
176+ tokens.getHiddenTokensToRight(stop.tokenIndex).orEmpty().isEmpty()
177+ } ? : true
178+
179+ toLeft && toRight
180+ } else {
181+ true
182+ }
183+ }
184+
165185 /* *
166186 * Use this in conjunction with [countTerminalNewlines] to maintain original end-of-file
167187 * formatting.
0 commit comments