1 /* 2 * 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.format 18 19 import org.jetbrains.kotlin.com.intellij.psi.PsiElement 20 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace 21 import org.jetbrains.kotlin.kdoc.psi.impl.KDocImpl 22 import org.jetbrains.kotlin.psi.KtImportList 23 import org.jetbrains.kotlin.psi.KtPackageDirective 24 import org.jetbrains.kotlin.psi.KtReferenceExpression 25 import org.jetbrains.kotlin.psi.KtTreeVisitorVoid 26 import org.jetbrains.kotlin.psi.psiUtil.endOffset 27 import org.jetbrains.kotlin.psi.psiUtil.startOffset 28 29 /** Removes elements that are not needed in the code, such as semicolons and unused imports. */ 30 object RedundantElementRemover { 31 /** Remove extra semicolons and unused imports, if enabled in the [options] */ dropRedundantElementsnull32 fun dropRedundantElements(code: String, options: FormattingOptions): String { 33 val file = Parser.parse(code) 34 val redundantImportDetector = RedundantImportDetector(enabled = options.removeUnusedImports) 35 val redundantSemicolonDetector = RedundantSemicolonDetector() 36 37 file.accept( 38 object : KtTreeVisitorVoid() { 39 override fun visitElement(element: PsiElement) { 40 if (element is KDocImpl) { 41 redundantImportDetector.takeKdoc(element) 42 } else { 43 redundantSemicolonDetector.takeElement(element) { super.visitElement(element) } 44 } 45 } 46 47 override fun visitPackageDirective(directive: KtPackageDirective) { 48 redundantImportDetector.takePackageDirective(directive) { 49 super.visitPackageDirective(directive) 50 } 51 } 52 53 override fun visitImportList(importList: KtImportList) { 54 redundantImportDetector.takeImportList(importList) { super.visitImportList(importList) } 55 } 56 57 override fun visitReferenceExpression(expression: KtReferenceExpression) { 58 redundantImportDetector.takeReferenceExpression(expression) 59 super.visitReferenceExpression(expression) 60 } 61 }) 62 63 val result = StringBuilder(code) 64 val elementsToRemove = 65 redundantSemicolonDetector.getRedundantSemicolonElements() + 66 redundantImportDetector.getRedundantImportElements() 67 68 for (element in elementsToRemove.sortedByDescending(PsiElement::endOffset)) { 69 // Don't insert extra newlines when the semicolon is already a line terminator 70 val replacement = if (element.nextSibling.containsNewline()) "" else "\n" 71 result.replace(element.startOffset, element.endOffset, replacement) 72 } 73 74 return result.toString() 75 } 76 containsNewlinenull77 private fun PsiElement?.containsNewline(): Boolean { 78 if (this !is PsiWhiteSpace) return false 79 return this.text.contains('\n') 80 } 81 } 82