diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java index 1f6c01625a0d..31ffdc25d1a7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/StringLiteralCopyPasteProcessor.java @@ -168,7 +168,7 @@ public class StringLiteralCopyPasteProcessor implements CopyPastePreProcessor { } @Nullable - private PsiElement findLiteralTokenType(PsiFile file, int selectionStart, int selectionEnd) { + protected PsiElement findLiteralTokenType(PsiFile file, int selectionStart, int selectionEnd) { final PsiElement elementAtSelectionStart = file.findElementAt(selectionStart); if (elementAtSelectionStart == null) { return null; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java index 26f7775ca063..79970a641ca3 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -860,17 +860,25 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { return; } - for (String part : parts) { - if (!GrStringUtil.parseStringCharacters(part, new StringBuilder(text.length()), null, !quote.equals("/"))) { - myHolder.createErrorAnnotation(literal, GroovyBundle.message("illegal.escape.character.in.string.literal")); - return; + for (String part : parts) { + if (literal instanceof GrRegex) { + if (!GrStringUtil.parseRegexCharacters(part, new StringBuilder(text.length()), null, literal.getText().startsWith("/"))) { + myHolder.createErrorAnnotation(literal, GroovyBundle.message("illegal.escape.character.in.string.literal")); + return; + } + } + else { + if (!GrStringUtil.parseStringCharacters(part, new StringBuilder(text.length()), null)) { + myHolder.createErrorAnnotation(literal, GroovyBundle.message("illegal.escape.character.in.string.literal")); + return; + } } } if (isSimpleString) { int[] offsets = new int[substring.length() + 1]; - boolean result = GrStringUtil.parseStringCharacters(substring, builder, offsets, !quote.equals("/")); + boolean result = GrStringUtil.parseStringCharacters(substring, builder, offsets); LOG.assertTrue(result); if (!builder.toString().endsWith(quote) || substring.charAt(offsets[builder.length() - quote.length()]) == '\\') { myHolder.createErrorAnnotation(literal, GroovyBundle.message("string.end.expected")); @@ -893,7 +901,7 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { } } - if (literal instanceof GrRegex) { + if (literal instanceof GrRegex && ((GrRegex)literal).getInjections().length > 0) { if (!GroovyConfigUtils.getInstance().isVersionAtLeast(literal, GroovyConfigUtils.GROOVY1_8)) { myHolder.createErrorAnnotation(literal, GroovyBundle.message("slashy.strings.with.injections.are.not.allowed.in.groovy.0", GroovyConfigUtils.getInstance().getSDKVersion(literal))); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java index 733a8c7ac585..a0288e997c3e 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java @@ -110,7 +110,6 @@ public class TypesUtil { static { ourPrimitiveTypesToClassNames.put(mSTRING_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mGSTRING_LITERAL, JAVA_LANG_STRING); - ourPrimitiveTypesToClassNames.put(mREGEX_LITERAL, JAVA_LANG_STRING); ourPrimitiveTypesToClassNames.put(mNUM_INT, JAVA_LANG_INTEGER); ourPrimitiveTypesToClassNames.put(mNUM_LONG, JAVA_LANG_LONG); ourPrimitiveTypesToClassNames.put(mNUM_FLOAT, JAVA_LANG_FLOAT); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralEscaper.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralEscaper.java index 68a60dac94ef..a7ff995ccf46 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralEscaper.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralEscaper.java @@ -19,9 +19,7 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.litera import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import com.intellij.psi.LiteralTextEscaper; -import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; public class GrLiteralEscaper extends LiteralTextEscaper { @@ -36,7 +34,7 @@ public class GrLiteralEscaper extends LiteralTextEscaper { ProperTextRange.assertProperRange(rangeInsideHost); String subText = rangeInsideHost.substring(myHost.getText()); outSourceOffsets = new int[subText.length() + 1]; - return GrStringUtil.parseStringCharacters(subText, outChars, outSourceOffsets, isStrictBackSlash()); + return GrStringUtil.parseStringCharacters(subText, outChars, outSourceOffsets); } @Override @@ -46,11 +44,6 @@ public class GrLiteralEscaper extends LiteralTextEscaper { return (result <= rangeInsideHost.getLength() ? result : rangeInsideHost.getLength()) + rangeInsideHost.getStartOffset(); } - protected boolean isStrictBackSlash() { - PsiElement child = myHost.getFirstChild(); - return child == null || myHost.getNode().getElementType() != GroovyElementTypes.REGEX; - } - public boolean isOneLine() { final Object value = myHost.getValue(); return value instanceof String && ((String)value).indexOf('\n') < 0; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralImpl.java index d97758a4f99d..a51a480e5255 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrLiteralImpl.java @@ -17,7 +17,6 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals; import com.intellij.lang.ASTNode; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry; import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; @@ -31,7 +30,6 @@ import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager; -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; @@ -112,30 +110,16 @@ public class GrLiteralImpl extends GrAbstractLiteral implements GrLiteral, PsiLa } else if (elemType == mSTRING_LITERAL) { if (!text.startsWith("'")) return null; - text = text.substring(1); - if (text.endsWith("'")) { - text = text.substring(0, text.length() - 1); - } + text = GrStringUtil.removeQuotes(text); StringBuilder chars = new StringBuilder(text.length()); - boolean result = GrStringUtil.parseStringCharacters(text, chars, null, true); + boolean result = GrStringUtil.parseStringCharacters(text, chars, null); return result ? chars.toString() : null; } else if (elemType == mGSTRING_LITERAL) { if (!text.startsWith("\"")) return null; - if (text.startsWith("\"\"\"")) { - text = StringUtil.trimEnd(text.substring(3), "\"\"\""); - } - else { - text = StringUtil.trimEnd(text.substring(1), "\""); - } + text = GrStringUtil.removeQuotes(text); StringBuilder chars = new StringBuilder(text.length()); - boolean result = GrStringUtil.parseStringCharacters(text, chars, null, true); - return result ? chars.toString() : null; - } - else if (elemType == mREGEX_LITERAL) { - text = StringUtil.trimStart(StringUtil.trimEnd(text, "/"), "/"); - StringBuilder chars = new StringBuilder(text.length()); - boolean result = GrStringUtil.parseStringCharacters(text, chars, null, false); + boolean result = GrStringUtil.parseStringCharacters(text, chars, null); return result ? chars.toString() : null; } return null; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrRegexImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrRegexImpl.java index 64dc0ba710f4..fcda166b8272 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrRegexImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrRegexImpl.java @@ -22,6 +22,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrRegex; +import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; + +import java.util.List; /** * @author ilyas @@ -58,7 +61,22 @@ public class GrRegexImpl extends GrStringImpl implements GrRegex { return null; } - return child.getText(); + final StringBuilder chars = new StringBuilder(); + final boolean isDollarSlash = child.getNode().getElementType() == GroovyTokenTypes.mREGEX_CONTENT; + GrStringUtil.parseRegexCharacters(child.getText(), chars, null, isDollarSlash); + return chars.toString(); + } + + @Override + public String[] getTextParts() { + List parts = findChildrenByType(GroovyTokenTypes.mREGEX_CONTENT); + + String[] result = new String[parts.size()]; + int i = 0; + for (PsiElement part : parts) { + result[i++] = part.getText(); + } + return result; } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringImpl.java index 161f3a5bded8..828f69368921 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/literals/GrStringImpl.java @@ -27,7 +27,7 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager; -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl; +import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import java.util.List; @@ -86,6 +86,17 @@ public class GrStringImpl extends GrAbstractLiteral implements GrString { } public Object getValue() { - return null; + if (findChildByClass(GrStringInjection.class) != null) return null; + + final PsiElement fchild = getFirstChild(); + if (fchild == null) return null; + + final PsiElement content = fchild.getNextSibling(); + if (content == null || content.getNode().getElementType() != GroovyTokenTypes.mGSTRING_CONTENT) return null; + + final String text = content.getText(); + StringBuilder chars = new StringBuilder(text.length()); + boolean result = GrStringUtil.parseStringCharacters(text, chars, null); + return result ? chars.toString() : null; } } \ No newline at end of file diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java index a610c0ab2be5..39af0a536906 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GrStringUtil.java @@ -182,7 +182,9 @@ public class GrStringUtil { if (sL >= qL * 2 && s.endsWith(quote)) { return s.substring(qL, sL - qL); } - return s; + else { + return s.substring(qL); + } } public static String addQuotes(String s, boolean forGString) { @@ -338,13 +340,95 @@ public class GrStringUtil { return ""; } + public static boolean parseRegexCharacters(@NotNull String chars, + @NotNull StringBuilder outChars, + @Nullable int[] sourceOffsets, + boolean escapeSlash) { + assert sourceOffsets == null || sourceOffsets.length == chars.length() + 1; + if (chars.indexOf('\\') < 0) { + outChars.append(chars); + if (sourceOffsets != null) { + for (int i = 0; i < sourceOffsets.length; i++) { + sourceOffsets[i] = i; + } + } + return true; + } + + int index = 0; + final int outOffset = outChars.length(); + while (index < chars.length()) { + char c = chars.charAt(index++); + if (sourceOffsets != null) { + sourceOffsets[outChars.length() - outOffset] = index - 1; + sourceOffsets[outChars.length() + 1 - outOffset] = index; + } + if (c != '\\') { + outChars.append(c); + continue; + } + if (index == chars.length()) { + outChars.append('\\'); + return true; + } + c = chars.charAt(index++); + switch (c) { + case '/': + if (escapeSlash) { + outChars.append(c); + if (sourceOffsets != null) { + sourceOffsets[outChars.length() - outOffset] = index; + } + } + else { + outChars.append('\\').append('/'); + } + + break; + case '\n': + //do nothing + if (sourceOffsets != null) { + sourceOffsets[outChars.length() - outOffset] = index; + } + break; + case 'u': + // uuuuu1234 is valid too + while (index != chars.length() && chars.charAt(index) == 'u') { + index++; + } + if (index + 4 <= chars.length()) { + try { + int code = Integer.parseInt(chars.substring(index, index + 4), 16); + //line separators are invalid here + if (code == 0x000a || code == 0x000d) return false; + c = chars.charAt(index); + if (c == '+' || c == '-') return false; + outChars.append((char)code); + index += 4; + + if (sourceOffsets != null) { + sourceOffsets[outChars.length() - outOffset] = index; + } + } + catch (Exception e) { + return false; + } + } + else { + return false; + } + break; + default: + outChars.append('\\').append(c); + } + } + return true; + } + /** * @see com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl#parseStringCharacters(String, StringBuilder, int[]) */ - public static boolean parseStringCharacters(@NotNull String chars, - @NotNull StringBuilder outChars, - @Nullable int[] sourceOffsets, - final boolean strictBackSlash) { + public static boolean parseStringCharacters(@NotNull String chars, @NotNull StringBuilder outChars, @Nullable int[] sourceOffsets) { assert sourceOffsets == null || sourceOffsets.length == chars.length()+1; if (chars.indexOf('\\') < 0) { outChars.append(chars); @@ -371,15 +455,31 @@ public class GrStringUtil { c = chars.charAt(index++); switch (c) { case'b': + outChars.append('\b'); + break; case't': + outChars.append('\t'); + break; case'n': + outChars.append('\n'); + break; case'f': + outChars.append('\f'); + break; case'r': + outChars.append('\r'); + break; case'"': + outChars.append('\"'); + break; case'\'': + outChars.append('\''); + break; case'$': + outChars.append('$'); + break; case'\\': - outChars.append(c); + outChars.append('\\'); break; case '\n': //do nothing @@ -442,9 +542,6 @@ public class GrStringUtil { } break; default: - if (!strictBackSlash) { - break; - } return false; } if (sourceOffsets != null) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/StringValueTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/StringValueTest.groovy new file mode 100644 index 000000000000..c6a6fda6911a --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/StringValueTest.groovy @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2011 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.plugins.groovy.lang + +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.annotations.Nullable +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral +import org.jetbrains.plugins.groovy.util.TestUtils + +/** + * @author Max Medvedev + */ +class StringValueTest extends LightCodeInsightFixtureTestCase { + @Override + protected String getBasePath() { + "${TestUtils.testDataPath}stringValues/" + } + + private void doTest(@Nullable String expected) { + def file = myFixture.configureByFile("${getTestName(false)}.groovy") as GroovyFile + def literal = file.statements[0] as GrLiteral + assertEquals expected, literal.value + } + + void testString0() { + doTest("fo\nabc \r \u1234 \" \' \" ") + } + + void testString1() { + doTest(null) + } + + void testString2() { + doTest(null) + } + + void testString3() { + doTest("fooabc") + } + + void testGString() { + doTest("abc \' \" ") + } + + void testSlashString() { + doTest("abc \\a \\n / abc \u1234 \$ \\\$ ") + } + + void _testDollarSlashString() { + doTest("/abc \\n \\o \u1234 abc \$ / \\\$ /") + } +} diff --git a/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy b/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy index 98534f3b05c0..41b4008da53c 100644 --- a/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy +++ b/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy @@ -12,4 +12,7 @@ ptint """""\n\a\t""" print """\n${x}\t""" print "dfg\$fg" print 'fg\$fg' -print """\n${x}\""" \ No newline at end of file +print (/abc\n\r\y\o \u12 /) +print (/abc\n\r\y\o \u1234 /) +print """\n${x}\""" + \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/DollarSlashString.groovy b/plugins/groovy/testdata/stringValues/DollarSlashString.groovy new file mode 100644 index 000000000000..59f4d9e0ae0d --- /dev/null +++ b/plugins/groovy/testdata/stringValues/DollarSlashString.groovy @@ -0,0 +1,2 @@ +$/abc \n \o \u1234 \ +abc $ / \$ /$ \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/GString.groovy b/plugins/groovy/testdata/stringValues/GString.groovy new file mode 100644 index 000000000000..a00520ffb675 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/GString.groovy @@ -0,0 +1 @@ +"abc ' \" " \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/SlashString.groovy b/plugins/groovy/testdata/stringValues/SlashString.groovy new file mode 100644 index 000000000000..133f17257d29 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/SlashString.groovy @@ -0,0 +1,2 @@ +/abc \a \n \/ \ + abc \u1234 $ \$ / \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/String0.groovy b/plugins/groovy/testdata/stringValues/String0.groovy new file mode 100644 index 000000000000..c25aab16e7f5 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/String0.groovy @@ -0,0 +1 @@ +'fo\nabc \r \u1234 \" \' \" ' \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/String1.groovy b/plugins/groovy/testdata/stringValues/String1.groovy new file mode 100644 index 000000000000..5d18a9efb522 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/String1.groovy @@ -0,0 +1 @@ +'fo\nabc \r \u123' \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/String2.groovy b/plugins/groovy/testdata/stringValues/String2.groovy new file mode 100644 index 000000000000..5f9985642b89 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/String2.groovy @@ -0,0 +1 @@ +'fo\nabc \i' \ No newline at end of file diff --git a/plugins/groovy/testdata/stringValues/String3.groovy b/plugins/groovy/testdata/stringValues/String3.groovy new file mode 100644 index 000000000000..e9fbbe87e144 --- /dev/null +++ b/plugins/groovy/testdata/stringValues/String3.groovy @@ -0,0 +1,2 @@ +'''foo\ +abc''' \ No newline at end of file