diff --git a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/BUILD.bazel b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/BUILD.bazel
index a3e21226cdf7..1e0afa25f4ae 100644
--- a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/BUILD.bazel
+++ b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/BUILD.bazel
@@ -23,6 +23,7 @@ jvm_library(
"//plugins/grazie/src/test:grazie-tests_test_lib",
"//plugins/kotlin/base/test:test_test_lib",
"//platform/core-api:core",
+ "//platform/analysis-api:analysis",
"//plugins/kotlin/test-framework:test-framework_test_lib",
],
runtime_deps = [":kotlin-grazie-tests"]
diff --git a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/intellij.kotlin.grazie.tests.iml b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/intellij.kotlin.grazie.tests.iml
index 3288c197bb7b..770ca87a182a 100644
--- a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/intellij.kotlin.grazie.tests.iml
+++ b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/intellij.kotlin.grazie.tests.iml
@@ -15,6 +15,7 @@
+
\ No newline at end of file
diff --git a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/test/org/jetbrains/kotlin/idea/fir/grazie/KotlinGrazieSupportTest28.kt b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/test/org/jetbrains/kotlin/idea/fir/grazie/KotlinGrazieSupportTest28.kt
index 38fe1f6a1713..16d8eae67ab2 100644
--- a/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/test/org/jetbrains/kotlin/idea/fir/grazie/KotlinGrazieSupportTest28.kt
+++ b/plugins/kotlin/grazie/intellij.kotlin.grazie.tests/test/org/jetbrains/kotlin/idea/fir/grazie/KotlinGrazieSupportTest28.kt
@@ -54,4 +54,22 @@ class KotlinGrazieSupportTest28 : GrazieTestBase(), ExpectedPluginModeProvider {
val content = TextExtractor.findTextAt(file, 10, TextContent.TextDomain.ALL)
assertEquals("foo | bar", TextContentTest.unknownOffsets(content))
}
+
+ fun `test meaningful single suggestion in RenameTo action`() {
+ myFixture.configureByText("a.kt", """
+ class A {
+ // tagret
+ val tagret = 1
+ }
+ """)
+ myFixture.checkHighlighting()
+ val intention = myFixture.findSingleIntention("Typo: Rename to 'target'")
+ myFixture.launchAction(intention)
+ myFixture.checkResult("""
+ class A {
+ // target
+ val target = 1
+ }
+ """)
+ }
}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/grazie/KotlinGrazieSupportTest.kt b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/grazie/KotlinGrazieSupportTest.kt
index 6847976d68c4..42989b378faf 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/grazie/KotlinGrazieSupportTest.kt
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/grazie/KotlinGrazieSupportTest.kt
@@ -48,4 +48,22 @@ class KotlinGrazieSupportTest28 : GrazieTestBase(), ExpectedPluginModeProvider {
val content = TextExtractor.findTextAt(file, 10, TextContent.TextDomain.ALL)
assertEquals("foo | bar", TextContentTest.unknownOffsets(content))
}
+
+ fun `test meaningful single suggestion in RenameTo action`() {
+ myFixture.configureByText("a.kt", """
+ class A {
+ // tagret
+ val tagret = 1
+ }
+ """)
+ myFixture.checkHighlighting()
+ val intention = myFixture.findSingleIntention("Typo: Rename to 'target'")
+ myFixture.launchAction(intention)
+ myFixture.checkResult("""
+ class A {
+ // target
+ val target = 1
+ }
+ """)
+ }
}
diff --git a/plugins/kotlin/idea/tests/testData/grazie/Constructs.kt b/plugins/kotlin/idea/tests/testData/grazie/Constructs.kt
index ac948b85cff5..674a127b7102 100644
--- a/plugins/kotlin/idea/tests/testData/grazie/Constructs.kt
+++ b/plugins/kotlin/idea/tests/testData/grazie/Constructs.kt
@@ -2,6 +2,17 @@
package ide.language.kotlin
+interface A {
+ val typpo: Int
+ fun typpo(typpo: Int)
+}
+
+class B : A {
+ // typos are ignored because of `override` keyword
+ override val typpo: Int = 0
+ override fun typpo(typpo: Int) { }
+}
+
val variableWithEror = "error"
fun erorFunction(eror: Int) {}
diff --git a/plugins/kotlin/spellchecker/src/org/jetbrains/kotlin/idea/spellchecker/KotlinSpellcheckingStrategy.kt b/plugins/kotlin/spellchecker/src/org/jetbrains/kotlin/idea/spellchecker/KotlinSpellcheckingStrategy.kt
index 1d4baf51c125..9cc8893926f2 100644
--- a/plugins/kotlin/spellchecker/src/org/jetbrains/kotlin/idea/spellchecker/KotlinSpellcheckingStrategy.kt
+++ b/plugins/kotlin/spellchecker/src/org/jetbrains/kotlin/idea/spellchecker/KotlinSpellcheckingStrategy.kt
@@ -6,51 +6,57 @@ import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiNameIdentifierOwner
+import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.spellchecker.inspections.PlainTextSplitter
+import com.intellij.spellchecker.inspections.Splitter
import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy
+import com.intellij.spellchecker.tokenizer.TokenConsumer
import com.intellij.spellchecker.tokenizer.Tokenizer
import com.intellij.spellchecker.tokenizer.TokenizerBase
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtParameterList
internal class KotlinSpellcheckingStrategy : SpellcheckingStrategy(), DumbAware {
private val plainTextTokenizer: Tokenizer = TokenizerBase(PlainTextSplitter.getInstance())
+ private val codeTokenizer: Tokenizer = object : TokenizerBase(PlainTextSplitter.getInstance()) {
+ override fun consumeToken(element: PsiElement, consumer: TokenConsumer, splitter: Splitter) {
+ consumer.consumeToken(element, true, splitter)
+ }
+ }
private val emptyTokenizer: Tokenizer = EMPTY_TOKENIZER
override fun getTokenizer(element: PsiElement): Tokenizer {
if (useTextLevelSpellchecking() && (element is PsiComment || element is KtLiteralStringTemplateEntry)) {
- // use [KotlinTextExtractor] and [GrazieTextLevelSpellCheckingExtension] if enabled for non-code constructs
+ // use [KotlinTextExtractor] and [SpellingTextChecker] if enabled for non-code constructs
return emptyTokenizer
}
return when (element) {
is PsiComment -> super.getTokenizer(element)
- is KtParameter -> {
- val function = (element.parent as? KtParameterList)?.parent as? KtNamedFunction
- when {
- function?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true -> emptyTokenizer
- else -> super.getTokenizer(element)
- }
- }
-
- is PsiNameIdentifierOwner -> {
- when {
- element is KtModifierListOwner && element.hasModifier(KtTokens.OVERRIDE_KEYWORD) -> emptyTokenizer
- else -> super.getTokenizer(element)
- }
- }
-
is KtLiteralStringTemplateEntry if !isInjectedLanguageFragment(element.parent) -> plainTextTokenizer
-
+ is LeafPsiElement -> {
+ if (element.elementType != KtTokens.IDENTIFIER) return emptyTokenizer
+ val parent = element.parent
+ if (parent is KtNameReferenceExpression) return emptyTokenizer
+ if (parent is KtParameter) {
+ val function = (parent.parent as? KtParameterList)?.parent as? KtNamedFunction
+ if (function is KtNamedFunction) return getTokenizer(function)
+ }
+ if (parent is KtModifierListOwner) return getTokenizer(parent)
+ codeTokenizer
+ }
else -> emptyTokenizer
}
}
- override fun useTextLevelSpellchecking(): Boolean {
- return Registry.`is`("spellchecker.grazie.enabled", false)
+ private fun getTokenizer(parent: KtModifierListOwner): Tokenizer = when {
+ parent.hasModifier(KtTokens.OVERRIDE_KEYWORD) -> emptyTokenizer
+ else -> codeTokenizer
}
+
+ override fun useTextLevelSpellchecking(): Boolean = Registry.`is`("spellchecker.grazie.enabled", false)
}
diff --git a/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java b/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java
index e1e42868c5e4..9cebb08ae8de 100644
--- a/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java
+++ b/spellchecker/src/com/intellij/spellchecker/quickfixes/RenameTo.java
@@ -136,7 +136,7 @@ public class RenameTo extends IntentionAndQuickFixAction implements Iconable, Ev
private void generateSuggestions(String name, PsiElement element) {
if (suggestions == null) {
- TextRange range = restoreRange(name);
+ TextRange range = restoreRange();
if (range == null) return;
this.suggestions = SpellCheckerManager.getInstance(pointer.getProject()).getSuggestions(typo)
.stream()
@@ -147,15 +147,13 @@ public class RenameTo extends IntentionAndQuickFixAction implements Iconable, Ev
}
}
- private @Nullable TextRange restoreRange(String currentElementName) {
+ private @Nullable TextRange restoreRange() {
PsiElement element = pointer.getElement();
Segment rangeRelativeToFile = this.rangeRelativeToFile.getRange();
if (element == null || rangeRelativeToFile == null) return null;
- int offset = element.getText().indexOf(currentElementName);
return TextRange.create(rangeRelativeToFile)
- .shiftLeft(element.getTextRange().getStartOffset())
- .shiftLeft(offset);
+ .shiftLeft(element.getTextRange().getStartOffset());
}
private void runRenamer(PsiElement element, String suggestion) {