diff --git a/ChangeLog.md b/ChangeLog.md index 79c1d0b718e..2d67f9f580e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -22,6 +22,7 @@ New features: - Enable injection from Java or Kotlin function declaration by annotating parameter with @Language annotation - [KT-11807](https://youtrack.jetbrains.com/issue/KT-11807) Use function body template when generating overriding functions with default body - [KT-12079](https://youtrack.jetbrains.com/issue/KT-12079) Support "Autowired members defined in invalid Spring bean" inspection on Kotlin declarations +- [KT-11704](https://youtrack.jetbrains.com/issue/KT-11704) Support file path references inside of Kotlin string literals Issues fixed: diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/FileRefInStringLiteral.kt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/FileRefInStringLiteral.kt new file mode 100644 index 00000000000..55b0ca76309 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/FileRefInStringLiteral.kt @@ -0,0 +1,6 @@ +// NUMBER: 1 +// EXIST: foo.txt + +fun foo() { + val s = "fo" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/bar.txt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/bar.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/foo.txt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/foo.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/FileRefInStringLiteralNoPrefix.kt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/FileRefInStringLiteralNoPrefix.kt new file mode 100644 index 00000000000..4221f011cb4 --- /dev/null +++ b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/FileRefInStringLiteralNoPrefix.kt @@ -0,0 +1,8 @@ +// NUMBER: 3 +// EXIST: foo.txt +// EXIST: bar.txt +// EXIST: FileRefInStringLiteralNoPrefix.kt + +fun foo() { + val s = "" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/bar.txt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/bar.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/foo.txt b/idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/foo.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-1.kt b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-1.kt new file mode 100644 index 00000000000..8decb697b9a --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-1.kt @@ -0,0 +1,3 @@ +fun foo() { + val s = "fosomething" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-2.kt b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-2.kt new file mode 100644 index 00000000000..3f8371e16c5 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter-2.kt @@ -0,0 +1 @@ +// Empty file to satisfy test framework requirements \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter.kt.after b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter.kt.after new file mode 100644 index 00000000000..36b8d1dcc22 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralEnter.kt.after @@ -0,0 +1,3 @@ +fun foo() { + val s = "foo.txt" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-1.kt b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-1.kt new file mode 100644 index 00000000000..8decb697b9a --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-1.kt @@ -0,0 +1,3 @@ +fun foo() { + val s = "fosomething" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-2.kt b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-2.kt new file mode 100644 index 00000000000..3f8371e16c5 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab-2.kt @@ -0,0 +1 @@ +// Empty file to satisfy test framework requirements \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab.kt.after b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab.kt.after new file mode 100644 index 00000000000..36b8d1dcc22 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/FileRefInStringLiteralTab.kt.after @@ -0,0 +1,3 @@ +fun foo() { + val s = "foo.txt" +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/bar.txt b/idea/idea-completion/testData/handlers/multifile/bar.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/testData/handlers/multifile/foo.txt b/idea/idea-completion/testData/handlers/multifile/foo.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java index bbb184ed18d..a157109b93f 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileJvmBasicCompletionTestGenerated.java @@ -125,6 +125,18 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ doTest(fileName); } + @TestMetadata("FileRefInStringLiteral") + public void testFileRefInStringLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/FileRefInStringLiteral/"); + doTest(fileName); + } + + @TestMetadata("FileRefInStringLiteralNoPrefix") + public void testFileRefInStringLiteralNoPrefix() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/FileRefInStringLiteralNoPrefix/"); + doTest(fileName); + } + @TestMetadata("GroovyClassNameCompletionFromDefaultPackage") public void testGroovyClassNameCompletionFromDefaultPackage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/GroovyClassNameCompletionFromDefaultPackage/"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt index c24030c6027..4d33fe6358c 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt @@ -85,6 +85,14 @@ class CompletionMultiFileHandlerTest : KotlinCompletionTestCase() { doTest('\t', "TestBundle.properties") } + fun testFileRefInStringLiteralEnter() { + doTest('\n', "foo.txt", "bar.txt") + } + + fun testFileRefInStringLiteralTab() { + doTest('\t', "foo.txt", "bar.txt") + } + fun testNotImportedExtension() { doTest() } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 37bdac8faee..8d0956027f6 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -395,6 +395,7 @@ + { override fun value(e: PsiElement): Boolean { if (e.language != KotlinLanguage.INSTANCE) return true if (KotlinListSelectioner.canSelect(e)) return false + + val elementType = e.node.elementType + if (elementType == KtTokens.REGULAR_STRING_PART || elementType == KtTokens.ESCAPE_SEQUENCE) return true + if (e is KtContainerNode) return false if (e.parent.firstChild.nextSibling == null && e.parent !is KtContainerNode) return false // skip nodes with the same range as their parent diff --git a/idea/src/org/jetbrains/kotlin/idea/references/KotlinFilePathReferenceContributor.kt b/idea/src/org/jetbrains/kotlin/idea/references/KotlinFilePathReferenceContributor.kt new file mode 100644 index 00000000000..48bdc30fdcd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/references/KotlinFilePathReferenceContributor.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.references + +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FilePathReferenceProvider +import com.intellij.util.ProcessingContext +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.psiUtil.getContentRange +import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes +import org.jetbrains.kotlin.psi.psiUtil.plainContent + +class KotlinFilePathReferenceContributor : AbstractKotlinReferenceContributor() { + object KotlinFilePathReferenceProvider : FilePathReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + if (element !is KtStringTemplateExpression) return PsiReference.EMPTY_ARRAY + if (!element.isPlainWithEscapes()) return PsiReference.EMPTY_ARRAY + return getReferencesByElement(element, element.plainContent, element.getContentRange().startOffset, true) + } + } + + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerReferenceProvider( + PlatformPatterns.psiElement(KtStringTemplateExpression::class.java), + KotlinFilePathReferenceProvider + ) + } +} diff --git a/idea/testData/resolve/references/fileRefInRawStringLiteral.Data.java b/idea/testData/resolve/references/fileRefInRawStringLiteral.Data.java new file mode 100644 index 00000000000..576000ea446 --- /dev/null +++ b/idea/testData/resolve/references/fileRefInRawStringLiteral.Data.java @@ -0,0 +1,3 @@ +class Foo { + +} \ No newline at end of file diff --git a/idea/testData/resolve/references/fileRefInRawStringLiteral.kt b/idea/testData/resolve/references/fileRefInRawStringLiteral.kt new file mode 100644 index 00000000000..ee1097565d7 --- /dev/null +++ b/idea/testData/resolve/references/fileRefInRawStringLiteral.kt @@ -0,0 +1,5 @@ +fun foo() { + val s = """idea/testData/resolve/references/fileRefInRawStringLiteral.Data.java""" +} + +// REF: /src//resolve/references.fileRefInRawStringLiteral.Data.java \ No newline at end of file diff --git a/idea/testData/resolve/references/fileRefInStringLiteral.Data.java b/idea/testData/resolve/references/fileRefInStringLiteral.Data.java new file mode 100644 index 00000000000..576000ea446 --- /dev/null +++ b/idea/testData/resolve/references/fileRefInStringLiteral.Data.java @@ -0,0 +1,3 @@ +class Foo { + +} \ No newline at end of file diff --git a/idea/testData/resolve/references/fileRefInStringLiteral.kt b/idea/testData/resolve/references/fileRefInStringLiteral.kt new file mode 100644 index 00000000000..384d4deac67 --- /dev/null +++ b/idea/testData/resolve/references/fileRefInStringLiteral.kt @@ -0,0 +1,5 @@ +fun foo() { + val s = "idea/testData/resolve/references/fileRefInStringLiteral.Data.java" +} + +// REF: /src//resolve/references.fileRefInStringLiteral.Data.java \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java index 0f033dddc2d..f550879b0f1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java @@ -179,6 +179,18 @@ public class ReferenceResolveTestGenerated extends AbstractReferenceResolveTest doTest(fileName); } + @TestMetadata("fileRefInRawStringLiteral.kt") + public void testFileRefInRawStringLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/resolve/references/fileRefInRawStringLiteral.kt"); + doTest(fileName); + } + + @TestMetadata("fileRefInStringLiteral.kt") + public void testFileRefInStringLiteral() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/resolve/references/fileRefInStringLiteral.kt"); + doTest(fileName); + } + @TestMetadata("GenericFunctionParameter.kt") public void testGenericFunctionParameter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/resolve/references/GenericFunctionParameter.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt index a2cc19d8c96..66c8acab120 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt @@ -17,33 +17,12 @@ package org.jetbrains.kotlin.psi import com.intellij.lang.html.HTMLLanguage -import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference -import junit.framework.TestCase import org.intellij.lang.regexp.RegExpLanguage import org.intellij.plugins.intelliLang.Configuration -import org.intellij.plugins.intelliLang.inject.InjectLanguageAction -import org.intellij.plugins.intelliLang.inject.UnInjectLanguageAction import org.intellij.plugins.intelliLang.inject.config.BaseInjection import org.intellij.plugins.intelliLang.inject.config.InjectionPlace -import org.intellij.plugins.intelliLang.references.FileReferenceInjector class KotlinInjectionTest : AbstractInjectionTest() { - fun testInjectUnInjectOnSimpleString() { - myFixture.configureByText("test.kt", - """val test = "simple" """) - TestCase.assertTrue(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file)) - TestCase.assertFalse(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file)) - - InjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file, FileReferenceInjector()) - TestCase.assertTrue(myFixture.getReferenceAtCaretPosition() is FileReference) - - TestCase.assertFalse(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file)) - TestCase.assertTrue(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file)) - - UnInjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file) - TestCase.assertNull(myFixture.getReferenceAtCaretPosition()) - } - fun testInjectionOnJavaPredefinedMethodWithAnnotation() = doInjectionPresentTest( """ val test1 = java.util.regex.Pattern.compile("pattern")