initial injection support (KT-2428): allow injection inside string literals
This commit is contained in:
Generated
+12
@@ -0,0 +1,12 @@
|
||||
<component name="libraryTable">
|
||||
<library name="intellilang-plugin">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/ideaSDK/plugins/IntelliLang/lib/IntelliLang.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$PROJECT_DIR$/ideaSDK/sources/sources.zip!/plugins/IntelliLang/java-support" />
|
||||
<root url="jar://$PROJECT_DIR$/ideaSDK/sources/sources.zip!/plugins/IntelliLang/src" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -17,9 +17,18 @@
|
||||
package org.jetbrains.jet.lang.psi;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.ElementManipulators;
|
||||
import com.intellij.psi.LiteralTextEscaper;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.JetNodeTypes;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
|
||||
|
||||
public class JetStringTemplateExpression extends JetExpressionImpl implements PsiLanguageInjectionHost {
|
||||
private static final TokenSet TOKENS_SUITABLE_FOR_INJECTION = TokenSet.create(JetNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY, JetNodeTypes.ESCAPE_STRING_TEMPLATE_ENTRY);
|
||||
|
||||
public class JetStringTemplateExpression extends JetExpressionImpl {
|
||||
public JetStringTemplateExpression(@NotNull ASTNode node) {
|
||||
super(node);
|
||||
}
|
||||
@@ -33,4 +42,27 @@ public class JetStringTemplateExpression extends JetExpressionImpl {
|
||||
public JetStringTemplateEntry[] getEntries() {
|
||||
return findChildrenByClass(JetStringTemplateEntry.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidHost() {
|
||||
ASTNode node = getNode();
|
||||
ASTNode child = node.getFirstChildNode().getTreeNext();
|
||||
while (child != null) {
|
||||
if (child.getElementType() == JetTokens.CLOSING_QUOTE) return true;
|
||||
if (!TOKENS_SUITABLE_FOR_INJECTION.contains(child.getElementType())) return false;
|
||||
child = child.getTreeNext();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiLanguageInjectionHost updateText(@NotNull String text) {
|
||||
return ElementManipulators.handleContentChange(this, text);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
|
||||
return new KotlinStringLiteralTextEscaper(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.lang.psi
|
||||
|
||||
import com.intellij.psi.LiteralTextEscaper
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import gnu.trove.TIntArrayList
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.getContentRange
|
||||
import org.jetbrains.jet.lang.psi.psiUtil.isSingleQuoted
|
||||
|
||||
public class KotlinStringLiteralTextEscaper(host: JetStringTemplateExpression): LiteralTextEscaper<JetStringTemplateExpression>(host) {
|
||||
private var sourceOffsets: IntArray? = null
|
||||
|
||||
override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean {
|
||||
val sourceOffsetsList = TIntArrayList()
|
||||
var sourceOffset = 0
|
||||
|
||||
for (child in myHost.getEntries()) {
|
||||
val childRange = TextRange.from(child.getStartOffsetInParent(), child.getTextLength())
|
||||
if (rangeInsideHost.getEndOffset() <= childRange.getStartOffset()) {
|
||||
break
|
||||
}
|
||||
if (childRange.getEndOffset() <= rangeInsideHost.getStartOffset()) {
|
||||
continue
|
||||
}
|
||||
when (child) {
|
||||
is JetLiteralStringTemplateEntry -> {
|
||||
val textRange = rangeInsideHost.intersection(childRange).shiftRight(-childRange.getStartOffset())
|
||||
outChars.append(child.getText(), textRange.getStartOffset(), textRange.getEndOffset())
|
||||
textRange.getLength().times {
|
||||
sourceOffsetsList.add(sourceOffset++)
|
||||
}
|
||||
}
|
||||
is JetEscapeStringTemplateEntry -> {
|
||||
if (!rangeInsideHost.contains(childRange)) {
|
||||
//don't allow injection if its range starts or ends inside escaped sequence
|
||||
return false
|
||||
}
|
||||
val unescaped = child.getUnescapedValue()
|
||||
outChars.append(unescaped)
|
||||
unescaped.length().times {
|
||||
sourceOffsetsList.add(sourceOffset)
|
||||
}
|
||||
sourceOffset += child.getTextLength()
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
sourceOffsetsList.add(sourceOffset)
|
||||
sourceOffsets = sourceOffsetsList.toNativeArray()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int {
|
||||
val offsets = sourceOffsets
|
||||
if (offsets == null || offsetInDecoded >= offsets.size()) return -1
|
||||
return Math.min(offsets[offsetInDecoded], rangeInsideHost.getLength()) + rangeInsideHost.getStartOffset()
|
||||
}
|
||||
|
||||
override fun getRelevantTextRange(): TextRange {
|
||||
return myHost.getContentRange()
|
||||
}
|
||||
|
||||
override fun isOneLine(): Boolean {
|
||||
return myHost.isSingleQuoted()
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.lang.psi.psiUtil
|
||||
|
||||
import com.intellij.psi.AbstractElementManipulator
|
||||
import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
|
||||
public class JetStringTemplateExpressionManipulator : AbstractElementManipulator<JetStringTemplateExpression>() {
|
||||
override fun handleContentChange(element: JetStringTemplateExpression, range: TextRange, newContent: String): JetStringTemplateExpression? {
|
||||
val node = element.getNode()
|
||||
val content = if (element.isSingleQuoted()) StringUtil.escapeStringCharacters(newContent) else newContent
|
||||
val oldText = node.getText()
|
||||
val newText = oldText.substring(0, range.getStartOffset()) + content + oldText.substring(range.getEndOffset())
|
||||
val expression = JetPsiFactory(element.getProject()).createExpression(newText)
|
||||
node.replaceAllChildrenToChildrenOf(expression.getNode())
|
||||
return node.getPsi(javaClass())
|
||||
}
|
||||
|
||||
override fun getRangeInElement(element: JetStringTemplateExpression): TextRange {
|
||||
return element.getContentRange()
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.lang.psi.psiUtil
|
||||
|
||||
import com.intellij.psi.AbstractElementManipulator
|
||||
import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
|
||||
public class StringTemplateExpressionManipulator: AbstractElementManipulator<JetStringTemplateExpression>() {
|
||||
override fun handleContentChange(element: JetStringTemplateExpression, range: TextRange, newContent: String): JetStringTemplateExpression? {
|
||||
val node = element.getNode()
|
||||
val content = if (node.getFirstChildNode().getTextLength() == 1) StringUtil.escapeStringCharacters(newContent) else newContent
|
||||
val oldText = node.getText()
|
||||
val newText = oldText.substring(0, range.getStartOffset()) + content + oldText.substring(range.getEndOffset())
|
||||
val expression = JetPsiFactory(element.getProject()).createExpression(newText)
|
||||
node.replaceAllChildrenToChildrenOf(expression.getNode())
|
||||
return node.getPsi(javaClass())
|
||||
}
|
||||
|
||||
override fun getRangeInElement(element: JetStringTemplateExpression): TextRange {
|
||||
return element.getContentRange()
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.PsiComment
|
||||
import org.jetbrains.jet.lang.resolve.calls.CallTransformer.CallForImplicitInvoke
|
||||
import com.intellij.openapi.util.TextRange
|
||||
|
||||
public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? {
|
||||
val calleeExpression = getCalleeExpression()
|
||||
@@ -409,4 +410,14 @@ public fun JetTypeReference?.isProbablyNothing(): Boolean {
|
||||
}
|
||||
|
||||
public fun JetUserType?.isProbablyNothing(): Boolean
|
||||
= this?.getReferencedName() == "Nothing"
|
||||
= this?.getReferencedName() == "Nothing"
|
||||
|
||||
public fun JetStringTemplateExpression.getContentRange(): TextRange {
|
||||
val start = getNode().getFirstChildNode().getTextLength()
|
||||
val lastChild = getNode().getLastChildNode()
|
||||
val length = getTextLength()
|
||||
return TextRange(start, if (lastChild.getElementType() == JetTokens.CLOSING_QUOTE) length - lastChild.getTextLength() else length)
|
||||
}
|
||||
|
||||
public fun JetStringTemplateExpression.isSingleQuoted(): Boolean
|
||||
= getNode().getFirstChildNode().getTextLength() == 1
|
||||
|
||||
+2
-2
@@ -24,6 +24,7 @@
|
||||
<orderEntry type="module" module-name="compiler-tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="jet.as.java.psi" />
|
||||
<orderEntry type="library" name="idea-full" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="intellilang-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="junit-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="testng-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="copyright-plugin" level="project" />
|
||||
@@ -44,5 +45,4 @@
|
||||
<orderEntry type="module" module-name="kotlin-android-plugin" />
|
||||
<orderEntry type="module" module-name="js.frontend" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -221,6 +221,8 @@
|
||||
<lang.foldingBuilder language="jet" implementationClass="org.jetbrains.jet.plugin.JetFoldingBuilder"/>
|
||||
<lang.formatter language="jet" implementationClass="org.jetbrains.jet.plugin.formatter.JetFormattingModelBuilder"/>
|
||||
<lang.findUsagesProvider language="jet" implementationClass="org.jetbrains.jet.plugin.findUsages.JetFindUsagesProvider"/>
|
||||
<lang.elementManipulator forClass="org.jetbrains.jet.lang.psi.JetStringTemplateExpression"
|
||||
implementationClass="org.jetbrains.jet.lang.psi.psiUtil.JetStringTemplateExpressionManipulator"/>
|
||||
<fileStructureGroupRuleProvider implementation="org.jetbrains.jet.plugin.findUsages.KotlinDeclarationGroupRuleProvider"/>
|
||||
<importFilteringRule implementation="org.jetbrains.jet.plugin.findUsages.JetImportFilteringRule"/>
|
||||
<lang.refactoringSupport language="jet" implementationClass="org.jetbrains.jet.plugin.refactoring.JetRefactoringSupportProvider"/>
|
||||
|
||||
@@ -105,5 +105,5 @@ public class DirectiveBasedActionUtils {
|
||||
}
|
||||
|
||||
private static final Collection<String> IRRELEVANT_ACTION_PREFIXES =
|
||||
Arrays.asList("Disable ", "Edit intention settings", "Edit inspection profile setting");
|
||||
Arrays.asList("Disable ", "Edit intention settings", "Edit inspection profile setting", "Inject Language/Reference");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.psi
|
||||
|
||||
import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.jet.plugin.JetLightProjectDescriptor
|
||||
import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import com.intellij.psi.ElementManipulators
|
||||
import org.junit.Assert.*
|
||||
import com.intellij.openapi.util.TextRange
|
||||
|
||||
public class StringTemplateExpressionManipulatorTest: JetLightCodeInsightFixtureTestCase() {
|
||||
public fun testSingleQuoted() {
|
||||
doTestContentChange("\"a\"", "b", "\"b\"")
|
||||
doTestContentChange("\"\"", "b", "\"b\"")
|
||||
doTestContentChange("\"a\"", "\t", "\"\\t\"")
|
||||
doTestContentChange("\"a\"", "\n", "\"\\n\"")
|
||||
doTestContentChange("\"a\"", "\\t", "\"\\\\t\"")
|
||||
}
|
||||
|
||||
public fun testUnclosedQuoted() {
|
||||
doTestContentChange("\"a", "b", "\"b")
|
||||
doTestContentChange("\"", "b", "\"b")
|
||||
doTestContentChange("\"a", "\t", "\"\\t")
|
||||
doTestContentChange("\"a", "\n", "\"\\n")
|
||||
doTestContentChange("\"a", "\\t", "\"\\\\t")
|
||||
}
|
||||
|
||||
public fun testTripleQuoted() {
|
||||
doTestContentChange("\"\"\"a\"\"\"", "b", "\"\"\"b\"\"\"")
|
||||
doTestContentChange("\"\"\"\"\"\"", "b", "\"\"\"b\"\"\"")
|
||||
doTestContentChange("\"\"\"a\"\"\"", "\t", "\"\"\"\t\"\"\"")
|
||||
doTestContentChange("\"\"\"a\"\"\"", "\n", "\"\"\"\n\"\"\"")
|
||||
doTestContentChange("\"\"\"a\"\"\"", "\\t", "\"\"\"\\t\"\"\"")
|
||||
}
|
||||
|
||||
public fun testUnclosedTripleQuoted() {
|
||||
doTestContentChange("\"\"\"a", "b", "\"\"\"b")
|
||||
doTestContentChange("\"\"\"", "b", "\"\"\"b")
|
||||
doTestContentChange("\"\"\"a", "\t", "\"\"\"\t")
|
||||
doTestContentChange("\"\"\"a", "\n", "\"\"\"\n")
|
||||
doTestContentChange("\"\"\"a", "\\t", "\"\"\"\\t")
|
||||
}
|
||||
|
||||
public fun testReplaceRange() {
|
||||
doTestContentChange("\"abc\"", "x", range = TextRange(2,3), expected = "\"axc\"")
|
||||
doTestContentChange("\"\"\"abc\"\"\"", "x", range = TextRange(4,5), expected = "\"\"\"axc\"\"\"")
|
||||
}
|
||||
|
||||
private fun doTestContentChange(original: String, newContent: String, expected: String, range: TextRange? = null) {
|
||||
val expression = JetPsiFactory(getProject()).createExpression(original) as JetStringTemplateExpression
|
||||
val manipulator = ElementManipulators.getNotNullManipulator(expression)
|
||||
val newExpression = if (range == null) manipulator.handleContentChange(expression, newContent) else manipulator.handleContentChange(expression, range, newContent)
|
||||
assertEquals(expected, newExpression.getText())
|
||||
}
|
||||
|
||||
override fun getProjectDescriptor() = JetLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.psi.injection
|
||||
|
||||
import org.jetbrains.jet.lang.psi.JetPsiFactory
|
||||
import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
|
||||
import org.junit.Assert.*
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import java.util.HashMap
|
||||
import org.jetbrains.jet.JetLiteFixture
|
||||
import org.jetbrains.jet.ConfigurationKind
|
||||
|
||||
public class StringInjectionHostTest: JetLiteFixture() {
|
||||
public fun testRegular() {
|
||||
with (quoted("")) {
|
||||
checkInjection("", mapOf(0 to 1))
|
||||
assertOneLine()
|
||||
}
|
||||
with (quoted("a")) {
|
||||
checkInjection("a", mapOf(0 to 1, 1 to 2))
|
||||
assertOneLine()
|
||||
}
|
||||
with (quoted("ab")) {
|
||||
checkInjection("ab", mapOf(0 to 1, 1 to 2, 2 to 3))
|
||||
checkInjection("a", mapOf(0 to 1, 1 to 2), rangeInHost = TextRange(1, 2))
|
||||
checkInjection("b", mapOf(0 to 2, 1 to 3), rangeInHost = TextRange(2, 3))
|
||||
assertOneLine()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testUnclosedSimpleLiteral() {
|
||||
assertFalse(stringExpression("\"").isValidHost());
|
||||
assertFalse(stringExpression("\"a").isValidHost());
|
||||
}
|
||||
|
||||
public fun testEscapeSequences() {
|
||||
with (quoted("\\t")) {
|
||||
checkInjection("\t", mapOf(0 to 1, 1 to 3))
|
||||
assertNoInjection(TextRange(1, 2))
|
||||
assertNoInjection(TextRange(2, 3))
|
||||
assertOneLine()
|
||||
}
|
||||
|
||||
with (quoted("a\\tb")) {
|
||||
checkInjection("a\tb", mapOf(0 to 1, 1 to 2, 2 to 4, 3 to 5))
|
||||
checkInjection("a", mapOf(0 to 1, 1 to 2), rangeInHost = TextRange(1, 2))
|
||||
assertNoInjection(TextRange(1, 3))
|
||||
checkInjection("a\t", mapOf(0 to 1, 1 to 2, 2 to 4), rangeInHost = TextRange(1, 4))
|
||||
checkInjection("\t", mapOf(0 to 2, 1 to 4), rangeInHost = TextRange(2, 4))
|
||||
checkInjection("\tb", mapOf(0 to 2, 1 to 4, 2 to 5), rangeInHost = TextRange(2, 5))
|
||||
assertOneLine()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testTripleQuotes() {
|
||||
with (tripleQuoted("")) {
|
||||
checkInjection("", mapOf(0 to 3))
|
||||
assertMultiLine()
|
||||
}
|
||||
with (tripleQuoted("a")) {
|
||||
checkInjection("a", mapOf(0 to 3, 1 to 4))
|
||||
assertMultiLine()
|
||||
}
|
||||
with (tripleQuoted("ab")) {
|
||||
checkInjection("ab", mapOf(0 to 3, 1 to 4, 2 to 5))
|
||||
checkInjection("a", mapOf(0 to 3, 1 to 4), rangeInHost = TextRange(3, 4))
|
||||
checkInjection("b", mapOf(0 to 4, 1 to 5), rangeInHost = TextRange(4, 5))
|
||||
assertMultiLine()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testEscapeSequenceInTripleQuotes() {
|
||||
with (tripleQuoted("\\t")) {
|
||||
checkInjection("\\t", mapOf(0 to 3, 1 to 4, 2 to 5))
|
||||
checkInjection("\\", mapOf(0 to 3, 1 to 4), rangeInHost = TextRange(3, 4))
|
||||
checkInjection("t", mapOf(0 to 4, 1 to 5), rangeInHost = TextRange(4, 5))
|
||||
assertMultiLine()
|
||||
}
|
||||
}
|
||||
|
||||
public fun testMultiLine() {
|
||||
with (tripleQuoted("a\nb")) {
|
||||
checkInjection("a\nb", mapOf(0 to 3, 1 to 4, 2 to 5, 3 to 6))
|
||||
assertMultiLine()
|
||||
}
|
||||
}
|
||||
|
||||
private fun quoted(s: String): JetStringTemplateExpression {
|
||||
return stringExpression("\"$s\"")
|
||||
}
|
||||
|
||||
private fun tripleQuoted(s: String): JetStringTemplateExpression {
|
||||
return stringExpression("\"\"\"$s\"\"\"")
|
||||
}
|
||||
|
||||
private fun stringExpression(s: String): JetStringTemplateExpression {
|
||||
return JetPsiFactory(getProject()).createExpression(s) as JetStringTemplateExpression
|
||||
}
|
||||
|
||||
private fun JetStringTemplateExpression.assertNoInjection(range: TextRange): JetStringTemplateExpression {
|
||||
assertTrue(isValidHost())
|
||||
assertFalse(createLiteralTextEscaper().decode(range, StringBuilder()))
|
||||
return this
|
||||
}
|
||||
|
||||
private fun JetStringTemplateExpression.assertOneLine() {
|
||||
assertTrue(createLiteralTextEscaper().isOneLine())
|
||||
}
|
||||
|
||||
private fun JetStringTemplateExpression.assertMultiLine() {
|
||||
assertFalse(createLiteralTextEscaper().isOneLine())
|
||||
}
|
||||
|
||||
//todo[nik] make private when KT-6382 is fixed
|
||||
fun JetStringTemplateExpression.checkInjection(decoded: String, targetToSourceOffsets: Map<Int,Int>, rangeInHost: TextRange? = null) {
|
||||
assertTrue(isValidHost())
|
||||
for (prefix in listOf("", "prefix")) {
|
||||
val escaper = createLiteralTextEscaper()
|
||||
val chars = StringBuilder(prefix)
|
||||
val range = rangeInHost ?: escaper.getRelevantTextRange()
|
||||
assertTrue(escaper.decode(range, chars))
|
||||
assertEquals(decoded, chars.substring(prefix.length()))
|
||||
val extendedOffsets = HashMap(targetToSourceOffsets)
|
||||
val beforeStart = targetToSourceOffsets.keySet().min()!! - 1
|
||||
if (beforeStart >= 0) {
|
||||
extendedOffsets[beforeStart] = -1
|
||||
}
|
||||
extendedOffsets[targetToSourceOffsets.keySet().max()!! + 1] = -1
|
||||
for ((target, source) in extendedOffsets) {
|
||||
assertEquals("Wrong source offset for $target", source, escaper.getOffsetInHost(target, range))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createEnvironment() = createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY)
|
||||
}
|
||||
Reference in New Issue
Block a user