Skip to content

Latest commit

 

History

History
453 lines (326 loc) · 15.4 KB

IDE_EVALUATION.md

File metadata and controls

453 lines (326 loc) · 15.4 KB

I automatically renamed EuropeBerlinTzFile to EuropeBerlinTzFile2023c in response to https://github.com/Kotlin/kotlinx-datetime/pull/286/files/613d49d06a0353cc930b5fb194b20101478639e6#diff-4cb9dd770dc64cf73071ca9c37793d0781b78e2e5d2d9cc37ab2d70d42f8987b

This could be done via sed, but it is less streamlined than Shift+Shift, EuropeBer, Enter, Shift+F6, right arrow, 2023c, Enter, and much less conceptually busy then git grep -l EuropeBer | xargs sed -i 's/\(EuropeBeR\W+\)/\1_2023c/g'.


I'm simplifying a class hierarchy. I want to remove an interface, as it's never actually used, and make all the methods that constitute its implementation just plain methods: transform

interface X {
  fun a()
}

class Y : X {
  override fun a()
}

to

class Y : X {
  fun a()
}

Trying to inline the interface via a context menu item or Ctrl+Shift+N while keeping the cursor on its name results in

Cannot perform refactoring. Caret should be positioned at the name of element to be refactored

Clearly misleading. Also, why then is the context menu item there at all?

Trying to "Safe delete" (Alt+Delete), I get notified that there is a usage and it's not safe to delete the interface. "Delete anyway" just results in broken code.

Oh well, let's do this manually.


I have a bunch of classes implementing an interface:

sealed interface X {
}

class A : X {
}

class B : X {
}

class C : X {
}

I want to move them inside the interface as nested classes:

sealed interface X {
    class A : X {
    }
    class B : X {
    }
    class C : X {
    }
}

F6 does not give me an option to move the classes inside.

Googling how to do this, I found https://youtrack.jetbrains.com/issue/KTIJ-9834. Ok, so no way to do this yet, and I need to manually move the classes and update the usages.

During the manual fixup, the chain of F2 to go to the next unresolved symbol and IdeaVim's . to repeat the previous command (in this case, it was prepending X.) worked well this time. Though this is more of a hack and not a structured solution: when I have to append something to the place with an error, it doesn't work nearly as well.


I used the IDE just now to launch a specific test. A neat feature.


I want to find the places in a codebase where a Deprecated.HIDDEN declaration is preceded by a docstring. In the console, I can do it roughly this way:

git grep -B3 DeprecationLevel.HIDDEN | grep -F '*/' | sed 's/\(.\+.kt\).*/\1/g' | sort -u

It finds the mentions of DeprecationLevel.HIDDEN, takes the three lines above that mention (in order to skip some other declarations), finds the comment-ending sequence */, leaves out the specific matches, keeping only the filenames, and produces a sorted list of them.

This is very hacky. I'd certainly like to employ a more structured approach. Something like comment annotation* @ReplaceWith(DeprecationLevel.HIDDEN).

The IDE contains the feature just like that: a structured search (and replace). Let's try it out, I never did so before.

The window for writing my search expression helpfully autocompletes. So, "comment"...

  • "Comments (Java search template)"
  • "Comments containing a given word (Java search template)"
  • "Comments containing a given word (Kotlin search template)"

I wonder what Java search templates are doing here, given that I explicitly set the language to Kotlin. Also, no "Comments (Kotlin search template)"? Won't Java's search template work?.. No, it won't, it just returns Java files.

Ok, "Comments containing a given word (Kotlin search template)" it is, with the word being "a":

// $before$ a $after$

"No modifiers added for $after$" What? Alright, doesn't matter, my needs are fairly simple.

Next, "Annotations".

Wait, why does choosing "Annotations (Kotlin search template)" from autocompletion removes the template I already had? Can I only search either/or? Probably not, I think I'm just choosing one template among the examples. Ok, let me read the examples provided on the left.

Looks like the principle is simple: you write the form of the code you want to find, using $variables$ to capture unknown input, and it will return you that code. For example,

// $comment$
@$Annotation$

should return comments followed by annotations...

    }

    @Test
    fun testAwaitFailure() = runBlocking {

Eh? I don't see any comment here. Maybe just comments don't work for Kotlin and you do need it to have a form // $before$ bug $after$?

// $comment1$ a $comment2$
@$Annotation$

No, @Test without any surrounding comments is still there for some reason.

What does the "Injected code" checkbox do? Time to read the documentation, I'm unable to make sense of this any other way, it seems.

https://www.jetbrains.com/help/idea/structural-search-and-replace.html

Injected code: if this checkbox is selected, the injected code such as JavaScript that is injected in HTML code or SQL injected in Java will be a part of the search process.

Yeah, not relevant at all. A small gripe is that it doesn't have a tooltip to indicate this on mouse hover.

https://resources.jetbrains.com/help/img/idea/2023.2/Edit_filters.png

Huh? What does the regex \b[A-Z].*?\b even mean? Which dialect is that? ? is supposed to mean "optionally", but .* already can have the width of zero... And why are \b needed? Isn't the field, surrounded by spaces, already known to be on a word boundary? No matter, probably a typo. Not to mention that I don't need any regexes here, my requirements are simple.

Unfortunately, the documentation does not answer my question at all. Let's try some more manually.

/** $comment1$ a $comment2$ */
@$Annotation$

also returns the uncommented @Test.

I tried setting comment1 to be the "target" of the search:

Target: in the list of options, select what item to search for.

However, even when explicitly searching comment1 instances, just the annotations are returned anyway.

I give up, I don't think this works. And here I was thinking about searching for public declarations without a KDoc...


I want to replace all occurrences of assertTrue(x is T) with assertIs<T>(x). This doesn't require any new imports. A simple, straightforward task. I could even do it from the command line in a minute if I so desired, but let's give the IDE a chance to shine.

I open "Search structurally." I input the template: assertTrue($Expr$ is $Type$). I click "Find." It found 201 results! Not bad.

There's a button: "Create Inspection from Template..." I'm interested: after all, I want to replace the problematic code.

Hm... I get a window that prompts me to input the inspection name, a description, and a tooltip, along with "Suppress ID." No, inspection doesn't seem to be what I want.

Wait, the template is wrong: I also want to capture the overload "assertTrue($Expr$ is $Type$, $error_message$)." I do this using the template "assertTrue($Expr$ is $Type$, $Parameter$)." It automatically recognizes that I need 0+ parameters. Now I get 214 results. How do I edit them, though?

Oh, it's a separate action: "Replace structurally." Let's try it.

The UI is weak: there deosn't seem to be a way to see the "before" and the "after" side-by-side. Still, "Replace All" did its job! Amazing!

...

assertTrue(args is Array<Int>) // before
(assertIs<Array>(args)) // after

Did I do something wrong? Why the extra parens? They don't appear in any other replacements. Also, more importantly, why is the type argument missing?

assertEquals(context[CoroutineName.Key]?.name, infoFromJson.name) // before
assertEquals(context[CoroutineName]?.name, infoFromJson.name) // after

Wait, what? Why did it do that? I didn't ask for this, I certainly didn't do this myself.

Let's see if there are any templates that allow me to explicitly capture the type arguments so that they don't get missing.

... You can't make this up: when I tried to look at the template called "Properties with explicit getter," the IDE hanged instead. Though this doesn't reproduce. Ok.

I didn't find a template, so I wrote this:

assertTrue($Expr$ is $Type$<$Parameter$>, $Parameter$)

For some reason, I got 209 matches. A weird number.

This doesn't seem to work:

assertTrue(cause is TestException1) // before
assertIs<TestException1<>>(cause) // after

So it's smart enough to understand that TestException1 matches the template $Type$<$Parameter$>, but not smart enough to understand that TestException1<> should be formatted as TestException1.

Ok, I give up. I've no idea why the IDE thinks it's okay to throw away information (and make arbitrary extra changes) when all I'm asking is to make a permutation of some parameters:

assertTrue($Expr$ is $Type$, $Parameter$)
assertIs<$Type$>($Expr$, $Parameter$)

I'm intimidated to even try to do the thing for which this was a warm-up: to look for the functions whose name starts with "assert" that accept a literal string with parameters.

With grep, it's...

git grep -P 'assert.+\(.*\$'

I'm sure it misses some cases (notably, ones where the strings is on a separate line), but at least it gives me something.

Ok... Here goes nothing.

Let's start with

$MethodCall$($Parameter$)

I "add a modifier" to $MethodCall$. It can be a "reference," "match call semantically," "text," or a "script." "Reference" I understand: some specific entity in code. Clicking at "Script," I see there's access to the full-fledged API. It's a good thing. "Match call semantically" is completely opaque for me. What I need is "Text." The info blob helpfully states that regular expressions are supported, but doesn't say which dialect. Ok, let's try "assert.+.

It looks like it searches for the right thing! It is extremely slow for some reason, but I'm not going to complain yet. Let's arrive at a somewhat working solution first.

There are two patterns that I see in the grep output:

assert.+(blah-blah, "Error")

and

assert.+(message = "Error") { }

Maybe I could achieve this by using the "script" option. Let's look into this. Googling "GroovyScript IntelliJ API" (a phrase verbatim from the infoblob that explains what "script" is), I get https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000045970-Search-and-Replace-Structurally-Script-Constraints-Documentation-: some forum post where a user decries lack of documentation, asks for pointers, and receives an (actually useful) explanation. Maybe I'll use this approach if I can't actually write the template.

$MethodCall$($Parameter$, "$String$") does seem to return vaguely relevant results. It also includes AssertionError (I forgot to tick the "match case" option) and assertEquals(channel.receive(), "3"), but this is expected. Now, how do I only keep the strings that contain templates?

Oh, well, maybe later. I have a more pressing matter right now. More on that below.


Here's the star hour for the IDE.

I need to move a bunch of definitions to a new package. Basically, I need to change the package of a whole Gradle module and update all the dependent files. This would be tough to do by hand: I'd need to find every place where something is used and add a new import.

Well, if the IDE fails, I know how to do that in this particular case. Still, it wouldn't be as clean a solution.

I open the file I want to move and press F6. I get the list of all definitions in this file. Am I supposed to tick every checkbox manually? Where's "select all"? There's, like, thirty of them! Oh, alright. Arrow down, space, arrow down, space...

The following problems were found:
Class TestBase uses property SHUTDOWN_TIMEOUT which will be inaccessible after move
Class TestBase uses property VERBOSE which will be inaccessible after move

Makes sense: I moved expect classes and their actual instantiations, but not the things on which these actual instantiations depend. I can live with that and move them manually.

What I can't live with is the rest of the result.

expect class was moved correctly to kotlinx.coroutines.testing from kotlinx.coroutines. The actual class was instead moved to kotlinx.coroutines.kotlinx.coroutines.testing. The newly-moved files have no import statements. I can fix all of this by git checkout-ing the old files and manually tweaking the package name.

The most important thing to me is that the dependent files do properly contain the imports of the new package that I introduced.

Except they don't. Yes, it was all in vain. I'll resort to CLI magic.

First, find every file that mentions the most common definition from the new package, excluding the directory that contains it:

git grep -l TestBase | grep -v test-utils

Next, let's try automatically adapting a single file the way I want. And I want to find the first import statement and add my import before it. Can I do that?

First, let's check that every file does have an import statement, at least one:

$ git grep -l TestBase | grep -v test-utils | xargs -n1 sh -c 'cat "$0" | grep -q "^import" || echo "$0"'
kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt

Yeah, I should exclude the .txt file as well. Other than that, good to go.

sed '0,/import/{/import/s/^/import kotlinx.coroutines.testing.*\n/}' kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt

Basically, "from the 0th line to the first mention of import" (so, until the first import), "if you see import, replace the beginning of the line with import kotlinx.coroutines.testing.*, followed by a newline." Looking at the result, it does seem to work.

And now run this in parallel:

git grep -l TestBase | grep -v test-utils | xargs -n1 sed -i '0,/import/{/import/s/^/import kotlinx.coroutines.testing.*\n/}'

Et voila!

Oh, wait, I need to fix the tests that do mention TestBase by its fully qualified name:

$ git grep -F .TestBase
kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt:  at kotlinx.coroutines.TestBase.runTest(TestBase.kt)
kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt:import kotlinx.coroutines.TestBase
kotlinx-coroutines-core/jvm/test/jdk8/time/FlowDebounceTest.kt:import kotlinx.coroutines.TestBase
kotlinx-coroutines-core/jvm/test/jdk8/time/FlowSampleTest.kt:import kotlinx.coroutines.TestBase
kotlinx-coroutines-debug/test/DebugProbesTest.kt:                        "\tat kotlinx.coroutines.TestBase.runTest(TestBase.kt)\n" +
kotlinx-coroutines-debug/test/DebugProbesTest.kt:                        "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt)\n" +
kotlinx-coroutines-debug/test/SanitizedProbesTest.kt:                    "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" +
kotlinx-coroutines-debug/test/SanitizedProbesTest.kt:                "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" +
kotlinx-coroutines-debug/test/SanitizedProbesTest.kt:                    "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" +
kotlinx-coroutines-debug/test/SanitizedProbesTest.kt:                "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:154)\n" +
ui/kotlinx-coroutines-javafx/test/JavaFxObservableAsFlowTest.kt:import kotlinx.coroutines.TestBase

Also easy:

git grep -lF .TestBase | xargs -n1 sed -i 's/kotlinx.coroutines.TestBase/kotlinx.coroutines.testing.TestBase/'

Aside from a couple more files, everything seems to work correctly.