@@ -10,6 +10,11 @@ import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
10
10
import com.pinterest.ktlint.core.ast.ElementType.KDOC
11
11
import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
12
12
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
13
+ import com.pinterest.ktlint.core.ast.children
14
+ import com.pinterest.ktlint.core.ast.isPartOfComment
15
+ import com.pinterest.ktlint.core.ast.isWhiteSpace
16
+ import com.pinterest.ktlint.core.ast.nextSibling
17
+ import com.pinterest.ktlint.core.ast.prevSibling
13
18
import org.cqfn.diktat.common.config.rules.RuleConfiguration
14
19
import org.cqfn.diktat.common.config.rules.RulesConfig
15
20
import org.cqfn.diktat.common.config.rules.getCommonConfiguration
@@ -21,7 +26,6 @@ import org.cqfn.diktat.ruleset.constants.Warnings.FILE_UNORDERED_IMPORTS
21
26
import org.cqfn.diktat.ruleset.constants.Warnings.FILE_WILDCARD_IMPORTS
22
27
import org.cqfn.diktat.ruleset.rules.PackageNaming.Companion.PACKAGE_SEPARATOR
23
28
import org.cqfn.diktat.ruleset.utils.StandardPlatforms
24
- import org.cqfn.diktat.ruleset.utils.findChildBefore
25
29
import org.cqfn.diktat.ruleset.utils.getFileName
26
30
import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder
27
31
import org.cqfn.diktat.ruleset.utils.moveChildBefore
@@ -43,7 +47,6 @@ import org.jetbrains.kotlin.psi.KtImportDirective
43
47
* 5. Ensures there are no wildcard imports
44
48
*/
45
49
class FileStructureRule (private val configRules : List <RulesConfig >) : Rule(" file-structure" ) {
46
-
47
50
private lateinit var emitWarn: ((offset: Int , errorMessage: String , canBeAutoCorrected: Boolean ) -> Unit )
48
51
private var isFixMode: Boolean = false
49
52
private var fileName: String = " "
@@ -87,45 +90,51 @@ class FileStructureRule(private val configRules: List<RulesConfig>) : Rule("file
87
90
}
88
91
89
92
private fun checkCodeBlocksOrderAndEmptyLines (node : ASTNode ) {
90
- // fixme handle other elements that could be present before package (other comments)
91
- val copyrightComment = node.findChildBefore(PACKAGE_DIRECTIVE , BLOCK_COMMENT )
92
- val headerKdoc = node.findChildBefore(PACKAGE_DIRECTIVE , KDOC )
93
- val fileAnnotations = node.findChildByType(FILE_ANNOTATION_LIST )
94
- // PACKAGE_DIRECTIVE node is always present in regular kt files and might be absent in kts
95
- // kotlin compiler itself enforces it's position in the file if it is present
96
- // fixme: handle cases when this node is not present
97
- val packageDirectiveNode = (node.psi as KtFile ).packageDirective?.node ? : return
98
- // fixme: find cases when node.psi.importLists.size > 1, handle cases when it's not present
99
- val importsList = (node.psi as KtFile ).importList?.node ? : return
93
+ // PACKAGE_DIRECTIVE node is always present in regular kt files and might be absent in kts.
94
+ // Kotlin compiler itself enforces it's position in the file if it is present.
95
+ // If package directive is missing in .kt file (default package), the node is still present in the AST.
96
+ // fixme: find and handle cases when this node is not present (.kts files?)
97
+ val packageDirectiveNode = (node.psi as KtFile ).packageDirective?.takeUnless { it.isRoot }?.node
98
+ // fixme: find cases when node.psi.importLists.size > 1, handle cases when it's not present (null)
99
+ val importsList = (node.psi as KtFile ).importList?.takeIf { it.imports.isNotEmpty() }?.node
100
+
101
+ // this node will be an anchor with respect to which we will look for all other nodes
102
+ val firstCodeNode = packageDirectiveNode
103
+ ? : importsList
104
+ ? : node.children().firstOrNull {
105
+ // taking nodes with actual code
106
+ ! it.isWhiteSpace() && ! it.isPartOfComment() &&
107
+ // but not the ones we are going to move
108
+ it.elementType != FILE_ANNOTATION_LIST && it.elementType != IMPORT_LIST &&
109
+ // if we are here, then package is default and we don't need to select the empty PACKAGE_DIRECTIVE node
110
+ it.elementType != PACKAGE_DIRECTIVE
111
+ }
112
+ ? : return // at this point it means the file contains only comments
113
+ // fixme: handle other elements that could be present before package (other comments)
114
+ var copyrightComment = firstCodeNode.prevSibling { it.elementType == BLOCK_COMMENT }
115
+ var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC }
116
+ var fileAnnotations = node.findChildByType(FILE_ANNOTATION_LIST )
100
117
101
118
// checking order
102
119
listOfNotNull(copyrightComment, headerKdoc, fileAnnotations).handleIncorrectOrder({
103
- getSiblingBlocks(copyrightComment, headerKdoc, fileAnnotations, packageDirectiveNode )
120
+ getSiblingBlocks(copyrightComment, headerKdoc, fileAnnotations, firstCodeNode )
104
121
}) { astNode, beforeThisNode ->
105
- FILE_INCORRECT_BLOCKS_ORDER .warnAndFix(
106
- configRules,
107
- emitWarn,
108
- isFixMode,
109
- astNode.text.lines().first(),
110
- astNode.startOffset,
111
- astNode
112
- ) {
113
- node.moveChildBefore(astNode, beforeThisNode, true )
122
+ FILE_INCORRECT_BLOCKS_ORDER .warnAndFix(configRules, emitWarn, isFixMode, astNode.text.lines().first(), astNode.startOffset, astNode) {
123
+ val result = node.moveChildBefore(astNode, beforeThisNode, true )
124
+ result.newNodes.first().run {
125
+ // reassign values to the nodes that could have been moved
126
+ when (elementType) {
127
+ BLOCK_COMMENT -> copyrightComment = this
128
+ KDOC -> headerKdoc = this
129
+ FILE_ANNOTATION_LIST -> fileAnnotations = this
130
+ }
131
+ }
114
132
astNode.treeNext?.let { node.replaceChild(it, PsiWhiteSpaceImpl (" \n\n " )) }
115
133
}
116
134
}
117
135
118
136
// checking empty lines
119
- arrayOf(copyrightComment, headerKdoc, fileAnnotations, packageDirectiveNode, importsList).forEach { astNode ->
120
- astNode?.treeNext?.apply {
121
- if (elementType == WHITE_SPACE && text.count { it == ' \n ' } != 2 ) {
122
- FILE_NO_BLANK_LINE_BETWEEN_BLOCKS .warnAndFix(configRules, emitWarn, isFixMode, astNode.text.lines().first(),
123
- astNode.startOffset, astNode) {
124
- (this as LeafPsiElement ).replaceWithText(" \n\n ${text.replace(" \n " , " " )} " )
125
- }
126
- }
127
- }
128
- }
137
+ insertNewlinesBetweenBlocks(listOf (copyrightComment, headerKdoc, fileAnnotations, packageDirectiveNode, importsList))
129
138
}
130
139
131
140
@Suppress(" UnsafeCallOnNullableType" )
@@ -186,6 +195,23 @@ class FileStructureRule(private val configRules: List<RulesConfig>) : Rule("file
186
195
}
187
196
}
188
197
198
+ private fun insertNewlinesBetweenBlocks (blocks : List <ASTNode ?>) {
199
+ blocks.forEach { astNode ->
200
+ // if package directive is missing, node is still present, but it's text is empty, so we need to check treeNext to get meaningful results
201
+ astNode?.nextSibling { it.text.isNotEmpty() }?.apply {
202
+ if (elementType == WHITE_SPACE && text.count { it == ' \n ' } != 2 ) {
203
+ FILE_NO_BLANK_LINE_BETWEEN_BLOCKS .warnAndFix(configRules, emitWarn, isFixMode, astNode.text.lines().first(),
204
+ astNode.startOffset, astNode) {
205
+ (this as LeafPsiElement ).replaceWithText(" \n\n ${text.replace(" \n " , " " )} " )
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ /* *
213
+ * @return a pair of nodes between which [this] node should be placed, i.e. after the first and before the second element
214
+ */
189
215
private fun ASTNode.getSiblingBlocks (
190
216
copyrightComment : ASTNode ? ,
191
217
headerKdoc : ASTNode ? ,
0 commit comments