Skip to content

ktfmt unused imports removal incorrectly removes import with backticks #532

@bhamiltoncx

Description

@bhamiltoncx

Kotlin allows arbitrarily escaping reserved words and symbols in import statements using backticks, e.g.

import foo.bar.baz

and

import `foo.bar.baz`

are both supported and work. However, using the latter form causes ktfmt's unused imports removal feature to incorrectly remove the import statement entirely:

% cat /tmp/Test.kt
import `blech.quux`
import `corge.grault` as xyzzy
import foo.bar.baz

fun doStuff() = baz + quux + xyzzy
% ktfmt - < /tmp/Test.kt 2>/dev/null
import `corge.grault` as xyzzy
import foo.bar.baz

fun doStuff() = baz + quux + xyzzy
% ktfmt --do-not-remove-unused-imports - < /tmp/Test.kt 2>/dev/null
import `blech.quux`
import `corge.grault` as xyzzy
import foo.bar.baz

fun doStuff() = baz + quux + xyzzy

Interestingly, this happens only if there is no alias at the end of the import statement.

I made a repro in 9e53104 . Here's the output of the test when it fails:

    KtImportDirective import `com.used.Foo.Bar.baz`
      LeafPsiElement import
      PsiWhiteSpaceImpl  
      KtNameReferenceExpression `com.used.Foo.Bar.baz`
        LeafPsiElement `com.used.Foo.Bar.baz`
    PsiWhiteSpaceImpl \n
    KtImportDirective import `com.used.Foo.Baz.blech` as quux
      LeafPsiElement import
      PsiWhiteSpaceImpl  
      KtNameReferenceExpression `com.used.Foo.Baz.blech`
        LeafPsiElement `com.used.Foo.Baz.blech`
      PsiWhiteSpaceImpl  
      KtImportAlias as quux
        LeafPsiElement as
        PsiWhiteSpaceImpl  
        LeafPsiElement quux
    PsiWhiteSpaceImpl \n

# Input: 
####################
import com.unused.Sample
import `com.used.Foo.Bar.baz`
import `com.used.Foo.Baz.blech` as quux
import com.used.FooBarBaz as Baz
import com.used.bar // test
import com.used.`class`
import com.used.a.*
import com.used.b as `if`
import com.used.b as we
import com.unused.a as `when`
import com.unused.a as wow

fun test(input: we) {
  Baz(`class`)
  `if` { bar }
  `else` { baz + quux }
  val x = unused()
}


# Output: 
####################
import `com.used.Foo.Baz.blech` as quux
import com.used.FooBarBaz as Baz
import com.used.a.*
import com.used.b as `if`
import com.used.b as we
import com.used.bar // test
import com.used.`class`

fun test(input: we) {
  Baz(`class`)
  `if` { bar }
  `else` { baz + quux }
  val x = unused()
}

# Expected: 
####################
import `com.used.Foo.Bar.baz`
import `com.used.Foo.Bar.blech` as quux
import com.used.FooBarBaz as Baz
import com.used.a.*
import com.used.b as `if`
import com.used.b as we
import com.used.bar // test
import com.used.`class`

fun test(input: we) {
  Baz(`class`)
  `if` { bar }
  `else` { baz + quux }
  val x = unused()
}

####################

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions