From fd53850860cf2ba0a174f011a64e4e5fd829ca13 Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Sun, 11 Dec 2011 12:47:59 +0400 Subject: [PATCH] checks for dollar-slash strings --- .../plugins/groovy/GroovyBundle.properties | 6 +- .../groovy/annotator/GroovyAnnotator.java | 126 +++++++++--------- .../intentions/AddParenthesesFix.java | 87 ++++++++++++ .../groovy/config/GroovyConfigUtils.java | 1 + .../groovy/lang/psi/util/GrStringUtil.java | 12 ++ .../highlighting/IncorrectEscaping.groovy | 6 +- 6 files changed, 172 insertions(+), 66 deletions(-) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/AddParenthesesFix.java diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties index 68c0760a128c..cd7d12081c37 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties @@ -294,4 +294,8 @@ Equals=Equals default.initializers.are.not.allowed.in.abstract.method=Default initializers are not allowed in abstract methods groovy.does.not.support.constructor.type.arguments=Groovy does not support constructor type arguments illegal.escape.character.in.string.literal=Illegal escape character in string literal -dollar.slash.end.expected=Dollar slash ending expected \ No newline at end of file +dollar.slash.end.expected=Dollar slash ending expected +regex.cannot.be.first.argument.of.command.method.call=Regex cannot be first argument of command method call +add.parentheses=Add parentheses +add.parentheses.to.command.method.call=Add parentheses to command method call +dollar.slash.strings.are.not.allowed.in.0=Dollar-slash strings are not allowed in Groovy {0} \ No newline at end of file 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 79970a641ca3..1f31f6b5780b 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -835,6 +835,63 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { checkStringLiteral(literal, text); } + @Override + public void visitRegexExpression(GrRegex regex) { + String text = regex.getText(); + String quote = GrStringUtil.getStartQuote(text); + + final GroovyConfigUtils config = GroovyConfigUtils.getInstance(); + + if ("$/".equals(quote)) { + if (!config.isVersionAtLeast(regex, GroovyConfigUtils.GROOVY1_9)) { + myHolder + .createErrorAnnotation(regex, GroovyBundle.message("dollar.slash.strings.are.not.allowed.in.0", config.getSDKVersion(regex))); + } + if (regex.getParent() instanceof GrCommandArgumentList && + ((GrCommandArgumentList)regex.getParent()).getAllArguments()[0] == regex) { + final Annotation annotation = + myHolder.createErrorAnnotation(regex, GroovyBundle.message("regex.cannot.be.first.argument.of.command.method.call")); + annotation.registerFix(new AddParenthesesFix()); + return; + } + } + + for (String part : regex.getTextParts()) { + if (!GrStringUtil.parseRegexCharacters(part, new StringBuilder(part.length()), null, regex.getText().startsWith("/"))) { + myHolder.createErrorAnnotation(regex, GroovyBundle.message("illegal.escape.character.in.string.literal")); + return; + } + } + + if ("/".equals(quote)) { + if (!config.isVersionAtLeast(regex, GroovyConfigUtils.GROOVY1_8)) { + if (text.contains("\n") || text.contains("\r")) { + myHolder.createErrorAnnotation(regex, GroovyBundle + .message("multiline.slashy.strings.are.not.allowed.in.groovy.0", config.getSDKVersion(regex))); + return; + } + } + } + + if (regex.getInjections().length > 0) { + if (!config.isVersionAtLeast(regex, GroovyConfigUtils.GROOVY1_8)) { + myHolder.createErrorAnnotation(regex, GroovyBundle + .message("slashy.strings.with.injections.are.not.allowed.in.groovy.0", config.getSDKVersion(regex))); + } + } + + } + + @Override + public void visitGStringExpression(GrString gstring) { + for (String part : gstring.getTextParts()) { + if (!GrStringUtil.parseStringCharacters(part, new StringBuilder(part.length()), null)) { + myHolder.createErrorAnnotation(gstring, GroovyBundle.message("illegal.escape.character.in.string.literal")); + return; + } + } + } + private void checkStringLiteral(PsiElement literal, String text) { StringBuilder builder = new StringBuilder(text.length()); @@ -842,71 +899,16 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { if (quote.isEmpty()) return; String substring = text.substring(quote.length()); - - String[] parts; - PsiElement child = literal.getFirstChild(); - if (child == null) child = literal; - IElementType elementType = child.getNode().getElementType(); - boolean isSimpleString = elementType == GroovyTokenTypes.mSTRING_LITERAL || - elementType == GroovyTokenTypes.mGSTRING_LITERAL || - elementType == GroovyTokenTypes.mREGEX_LITERAL; - if (isSimpleString) { - parts = new String[]{substring}; - } - else if (literal instanceof GrString) { - parts = ((GrString)literal).getTextParts(); - } - else { + if (!GrStringUtil.parseStringCharacters(substring, new StringBuilder(text.length()), null)) { + 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); - LOG.assertTrue(result); - if (!builder.toString().endsWith(quote) || substring.charAt(offsets[builder.length() - quote.length()]) == '\\') { - myHolder.createErrorAnnotation(literal, GroovyBundle.message("string.end.expected")); - return; - } - } - else { - LOG.assertTrue(literal instanceof GrString); - //absence of closing quote is registered by lexer - } - - - if (quote.equals("/")) { - if (!GroovyConfigUtils.getInstance().isVersionAtLeast(literal, GroovyConfigUtils.GROOVY1_8)) { - if (text.contains("\n") || text.contains("\r")) { - myHolder.createErrorAnnotation(literal, GroovyBundle.message("multiline.slashy.strings.are.not.allowed.in.groovy.0", - GroovyConfigUtils.getInstance().getSDKVersion(literal))); - return; - } - } - } - - 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))); - return; - } + int[] offsets = new int[substring.length() + 1]; + 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")); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/AddParenthesesFix.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/AddParenthesesFix.java new file mode 100644 index 000000000000..c5da9bcce990 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/AddParenthesesFix.java @@ -0,0 +1,87 @@ +/* + * 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.annotator.intentions; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.GroovyBundle; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCommandArgumentList; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; + +/** + * @author Max Medvedev + */ +public class AddParenthesesFix implements IntentionAction { + private static final Logger LOG = Logger.getInstance(AddParenthesesFix.class); + + @NotNull + @Override + public String getText() { + return GroovyBundle.message("add.parentheses"); + } + + @NotNull + @Override + public String getFamilyName() { + return GroovyBundle.message("add.parentheses.to.command.method.call"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + final int offset = editor.getCaretModel().getOffset(); + final PsiElement at = file.findElementAt(offset); + final GrCommandArgumentList argList = PsiTreeUtil.getParentOfType(at, GrCommandArgumentList.class); + return argList != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final int offset = editor.getCaretModel().getOffset(); + final PsiElement at = file.findElementAt(offset); + final GrCommandArgumentList argList = PsiTreeUtil.getParentOfType(at, GrCommandArgumentList.class); + if (argList == null) return; + + final PsiElement parent = argList.getParent(); + LOG.assertTrue(parent instanceof GrApplicationStatement); + + final GrExpression newExpr; + try { + newExpr = GroovyPsiElementFactory.getInstance(project) + .createExpressionFromText(((GrApplicationStatement)parent).getInvokedExpression().getText() + '(' + argList.getText() + ')'); + } + catch (IncorrectOperationException e) { + LOG.error(e); + return; + } + + parent.replace(newExpr); + editor.getCaretModel().moveToOffset(offset + 1); + } + + @Override + public boolean startInWriteAction() { + return true; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java index 5d5319261683..25faa13a946a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyConfigUtils.java @@ -52,6 +52,7 @@ public abstract class GroovyConfigUtils extends AbstractConfigUtils { public static final String NO_VERSION = ""; public static final String GROOVY1_7 = "1.7"; public static final String GROOVY1_8 = "1.8"; + public static final String GROOVY1_9 = "1.9"; private GroovyConfigUtils() { } 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 39af0a536906..f7bee66d167d 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 @@ -29,6 +29,8 @@ public class GrStringUtil { private static final String DOUBLE_QUOTES = "\""; private static final String TRIPLE_DOUBLE_QUOTES = "\"\"\""; private static final String SLASH = "/"; + private static final String DOLLAR_SLASH = "$/"; + private static final String SLASH_DOLLAR = "/$"; private GrStringUtil() { } @@ -179,6 +181,15 @@ public class GrStringUtil { String quote = getStartQuote(s); int sL = s.length(); int qL = quote.length(); + if (sL >= qL * 2 && DOLLAR_SLASH.equals(quote)) { + if (s.endsWith(SLASH_DOLLAR)) { + return s.substring(qL, sL - qL); + } + else { + return s.substring(qL); + } + } + if (sL >= qL * 2 && s.endsWith(quote)) { return s.substring(qL, sL - qL); } @@ -337,6 +348,7 @@ public class GrStringUtil { if (text.startsWith(TRIPLE_DOUBLE_QUOTES)) return TRIPLE_DOUBLE_QUOTES; if (text.startsWith(DOUBLE_QUOTES)) return DOUBLE_QUOTES; if (text.startsWith(SLASH)) return SLASH; + if (text.startsWith(DOLLAR_SLASH)) return DOLLAR_SLASH; return ""; } diff --git a/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy b/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy index 41b4008da53c..e31518de0622 100644 --- a/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy +++ b/plugins/groovy/testdata/highlighting/IncorrectEscaping.groovy @@ -1,14 +1,14 @@ print '\n' print '\y' ptint "\n\a\t" -print "\n\a${x}\t" +print "\n\a${x}\t" print "\n${x}\t" print "\n${x}\" print "\n\" print '''\n''' print '''\y''' -print """\n\a${x}\t""" -ptint """""\n\a\t""" +print """\n\a${x}\t""" +ptint """""\n\a\t""" print """\n${x}\t""" print "dfg\$fg" print 'fg\$fg'