type & value inference for literals

This commit is contained in:
Maxim.Medvedev
2011-12-11 12:54:30 +04:00
parent f4bff38d55
commit c68eb4298c
17 changed files with 238 additions and 49 deletions
@@ -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;
@@ -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)));
@@ -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);
@@ -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<GrLiteralImpl> {
@@ -36,7 +34,7 @@ public class GrLiteralEscaper extends LiteralTextEscaper<GrLiteralImpl> {
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<GrLiteralImpl> {
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;
@@ -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;
@@ -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<PsiElement> parts = findChildrenByType(GroovyTokenTypes.mREGEX_CONTENT);
String[] result = new String[parts.size()];
int i = 0;
for (PsiElement part : parts) {
result[i++] = part.getText();
}
return result;
}
}
@@ -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;
}
}
@@ -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) {
@@ -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 \$ / \\\$ /")
}
}
@@ -12,4 +12,7 @@ ptint """""\n\a\t"""
print """\n${x}\t"""
print "dfg\$fg"
print 'fg\$fg'
print """\n${x}\"""<EOLError descr="String end expected"></EOLError>
print (<error descr="Illegal escape character in string literal">/abc\n\r\y\o \u12 /</error>)
print (/abc\n\r\y\o \u1234 /)
print """\n${x}\"""
<EOLError descr="String end expected"></EOLError>
@@ -0,0 +1,2 @@
$/abc \n \o \u1234 \
abc $ / \$ /$
+1
View File
@@ -0,0 +1 @@
"abc ' \" "
@@ -0,0 +1,2 @@
/abc \a \n \/ \
abc \u1234 $ \$ /
+1
View File
@@ -0,0 +1 @@
'fo\nabc \r \u1234 \" \' \" '
+1
View File
@@ -0,0 +1 @@
'fo\nabc \r \u123'
+1
View File
@@ -0,0 +1 @@
'fo\nabc \i'
+2
View File
@@ -0,0 +1,2 @@
'''foo\
abc'''