@@ -4,9 +4,11 @@ import com.squareup.cash.grammar.KotlinLexer
4
4
import com.squareup.cash.grammar.KotlinParser.KotlinFileContext
5
5
import com.squareup.cash.grammar.KotlinParser.ScriptContext
6
6
import com.squareup.cash.grammar.KotlinParser.SemiContext
7
+ import com.squareup.cash.grammar.KotlinParser.SemisContext
7
8
import org.antlr.v4.runtime.CommonTokenStream
8
9
import org.antlr.v4.runtime.ParserRuleContext
9
10
import org.antlr.v4.runtime.Token
11
+ import org.antlr.v4.runtime.tree.ParseTree
10
12
11
13
/* *
12
14
* Utilities for working with whitespace, including newlines, carriage returns, etc.
@@ -119,49 +121,67 @@ public object Whitespace {
119
121
* Use this in conjunction with [trimGently] to maintain original end-of-file formatting.
120
122
*/
121
123
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()
123
139
}
124
140
125
141
/* *
126
142
* Use this in conjunction with [trimGently] to maintain original end-of-file formatting.
127
143
*/
128
144
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 {
133
145
return ctx.children
134
146
// Start iterating from EOF
135
147
.reversed()
136
148
.asSequence()
137
149
// Drop `EOF` (every file must have this)
138
150
.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) }
139
154
// Take only the "semis", which is a semi-colon or a newline, followed by 0 or more newlines
140
155
.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)
158
157
}
159
- .filterIsInstance<SemiContext >()
158
+ // Note this is a `SemisContext` (plural!)
159
+ .filterIsInstance<SemisContext >()
160
160
// This is the "a newline, followed by 0 or more newlines"
161
161
.flatMap { it.NL () }
162
162
.count()
163
163
}
164
164
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
+
165
185
/* *
166
186
* Use this in conjunction with [countTerminalNewlines] to maintain original end-of-file
167
187
* formatting.
0 commit comments