Don't run code analysis for injections with interpolation in string formatting (PY-11962)

This commit is contained in:
Andrey Vlasovskikh
2014-01-29 20:58:08 +04:00
parent e4bf6e4be6
commit 6e66b227d6
7 changed files with 99 additions and 25 deletions
@@ -10,6 +10,7 @@
<orderEntry type="module" module-name="core-api" />
<orderEntry type="module" module-name="IntelliLang" />
<orderEntry type="module" module-name="python-community" />
<orderEntry type="module" module-name="lang-impl" />
</component>
</module>
@@ -1,7 +1,10 @@
package com.jetbrains.python.intelliLang;
import com.intellij.lang.Language;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
import com.jetbrains.python.codeInsight.PyInjectorBase;
import org.intellij.plugins.intelliLang.Configuration;
import org.intellij.plugins.intelliLang.inject.InjectedLanguage;
@@ -15,6 +18,14 @@ import org.jetbrains.annotations.Nullable;
* @author vlan
*/
public class PyConfigurationInjector extends PyInjectorBase {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
final PyInjectionUtil.InjectionResult result = registerInjection(registrar, context);
if (!result.isStrict()) {
InjectorUtils.putInjectedFileUserData(registrar, InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
}
@Nullable
@Override
public Language getInjectedLanguage(@NotNull PsiElement context) {
@@ -5,6 +5,8 @@ import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
import com.jetbrains.python.codeInsight.PyInjectorBase;
import org.intellij.plugins.intelliLang.inject.InjectedLanguage;
import org.intellij.plugins.intelliLang.inject.InjectorUtils;
@@ -18,9 +20,13 @@ import org.jetbrains.annotations.Nullable;
public class PyTemporaryInjector extends PyInjectorBase {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
if (registerInjection(registrar, context)) {
final PyInjectionUtil.InjectionResult result = registerInjection(registrar, context);
if (result.isInjected()) {
final TemporaryPlacesRegistry registry = TemporaryPlacesRegistry.getInstance(context.getProject());
InjectorUtils.registerSupport(registry.getLanguageInjectionSupport(), false, registrar);
if (!result.isStrict()) {
InjectorUtils.putInjectedFileUserData(registrar, InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
}
}
@@ -32,6 +32,31 @@ import static com.jetbrains.python.inspections.PyStringFormatParser.*;
* @author vlan
*/
public class PyInjectionUtil {
public static class InjectionResult {
public static InjectionResult EMPTY = new InjectionResult(false, true);
private final boolean myInjected;
private final boolean myStrict;
private InjectionResult(boolean injected, boolean strict) {
myInjected = injected;
myStrict = strict;
}
public boolean isInjected() {
return myInjected;
}
public boolean isStrict() {
return myStrict;
}
public InjectionResult append(@NotNull InjectionResult result) {
return new InjectionResult(myInjected || result.isInjected(), myStrict && result.isStrict());
}
}
public static final List<Class<? extends PyExpression>> ELEMENTS_TO_INJECT_IN =
Arrays.asList(PyStringLiteralExpression.class, PyParenthesizedExpression.class, PyBinaryExpression.class, PyCallExpression.class);
@@ -54,8 +79,9 @@ public class PyInjectionUtil {
* Registers language injections in the given registrar for the specified string literal element or its ancestor that contains
* string concatenations or formatting.
*/
public static void registerStringLiteralInjection(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) {
processStringLiteral(element, registrar, "", "", Formatting.PERCENT);
@NotNull
public static InjectionResult registerStringLiteralInjection(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) {
return processStringLiteral(element, registrar, "", "", Formatting.PERCENT);
}
private static boolean isStringLiteralPart(@NotNull PsiElement element, @Nullable PsiElement context) {
@@ -101,10 +127,13 @@ public class PyInjectionUtil {
return null;
}
private static void processStringLiteral(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar, @NotNull String prefix,
@NotNull String suffix, @NotNull Formatting formatting) {
final String missingValue = "missing";
@NotNull
private static InjectionResult processStringLiteral(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar,
@NotNull String prefix, @NotNull String suffix, @NotNull Formatting formatting) {
final String missingValue = "missing_value";
if (element instanceof PyStringLiteralExpression) {
boolean injected = false;
boolean strict = true;
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
final List<TextRange> ranges = expr.getStringValueTextRanges();
final String text = expr.getText();
@@ -112,9 +141,8 @@ public class PyInjectionUtil {
if (formatting != Formatting.NONE) {
final String part = range.substring(text);
final List<FormatStringChunk> chunks = formatting == Formatting.NEW_STYLE ? parseNewStyleFormat(part) : parsePercentFormat(part);
if (chunks.isEmpty()) {
registrar.addPlace(prefix, suffix, expr, range);
continue;
if (!filterSubstitutions(chunks).isEmpty()) {
strict = false;
}
for (int i = 0; i < chunks.size(); i++) {
final FormatStringChunk chunk = chunks.get(i);
@@ -141,18 +169,21 @@ public class PyInjectionUtil {
}
final TextRange chunkRange = chunk.getTextRange().shiftRight(range.getStartOffset());
registrar.addPlace(chunkPrefix, chunkSuffix, expr, chunkRange);
injected = true;
}
}
}
else {
registrar.addPlace(prefix, suffix, expr, range);
injected = true;
}
}
return new InjectionResult(injected, strict);
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
if (contained != null) {
processStringLiteral(contained, registrar, prefix, suffix, formatting);
return processStringLiteral(contained, registrar, prefix, suffix, formatting);
}
}
else if (element instanceof PyBinaryExpression) {
@@ -162,23 +193,26 @@ public class PyInjectionUtil {
final boolean isLeftString = isStringLiteralPart(left, null);
if (expr.isOperator("+")) {
final boolean isRightString = right != null && isStringLiteralPart(right, null);
InjectionResult result = InjectionResult.EMPTY;
if (isLeftString) {
processStringLiteral(left, registrar, prefix, isRightString ? "" : missingValue, formatting);
result = result.append(processStringLiteral(left, registrar, prefix, isRightString ? "" : missingValue, formatting));
}
if (isRightString) {
processStringLiteral(right, registrar, isLeftString ? "" : missingValue, suffix, formatting);
result = result.append(processStringLiteral(right, registrar, isLeftString ? "" : missingValue, suffix, formatting));
}
return result;
}
else if (expr.isOperator("%")) {
processStringLiteral(left, registrar, prefix, suffix, Formatting.PERCENT);
return processStringLiteral(left, registrar, prefix, suffix, Formatting.PERCENT);
}
}
else if (element instanceof PyCallExpression) {
final PyExpression qualifier = getFormatCallQualifier((PyCallExpression)element);
if (qualifier != null) {
processStringLiteral(qualifier, registrar, prefix, suffix, Formatting.NEW_STYLE);
return processStringLiteral(qualifier, registrar, prefix, suffix, Formatting.NEW_STYLE);
}
}
return InjectionResult.EMPTY;
}
private enum Formatting {
@@ -27,17 +27,19 @@ public abstract class PyInjectorBase implements MultiHostInjector {
@Nullable
public abstract Language getInjectedLanguage(@NotNull PsiElement context);
protected boolean registerInjection(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
protected PyInjectionUtil.InjectionResult registerInjection(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
final Language language = getInjectedLanguage(context);
if (language != null) {
final PsiElement element = PyInjectionUtil.getLargestStringLiteral(context);
if (element != null) {
registrar.startInjecting(language);
PyInjectionUtil.registerStringLiteralInjection(element, registrar);
registrar.doneInjecting();
return true;
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(element, registrar);
if (result.isInjected()) {
registrar.doneInjecting();
}
return result;
}
}
return false;
return PyInjectionUtil.InjectionResult.EMPTY;
}
}
@@ -18,8 +18,13 @@ package com.jetbrains.python.codeInsight.regexp;
import com.intellij.lang.Language;
import com.intellij.lang.injection.MultiHostInjector;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.impl.source.tree.injected.MultiHostRegistrarImpl;
import com.intellij.psi.impl.source.tree.injected.Place;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
@@ -81,8 +86,16 @@ public class PythonRegexpInjector implements MultiHostInjector {
element.getContainingFile().getName().equals("re.py") && isRegexpMethod(element, index)) {
final Language language = isVerbose(call) ? PythonVerboseRegexpLanguage.INSTANCE : PythonRegexpLanguage.INSTANCE;
registrar.startInjecting(language);
PyInjectionUtil.registerStringLiteralInjection(context, registrar);
registrar.doneInjecting();
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(context, registrar);
if (result.isInjected()) {
registrar.doneInjecting();
if (!result.isStrict()) {
final PsiFile file = getInjectedFile(registrar);
if (file != null) {
file.putUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
}
}
}
}
}
@@ -90,6 +103,13 @@ public class PythonRegexpInjector implements MultiHostInjector {
}
}
@Nullable
private static PsiFile getInjectedFile(@NotNull MultiHostRegistrar registrar) {
// Don't add a dependency on IntelliLang here now, but this injector should become IntelliLang-based in the future
final List<Pair<Place,PsiFile>> result = ((MultiHostRegistrarImpl)registrar).getResult();
return result == null || result.isEmpty() ? null : result.get(result.size() - 1).second;
}
@NotNull
@Override
public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
@@ -127,7 +127,7 @@ public class PyRegexpTest extends PyTestCase {
"\n" +
"def f(x, y):\n" +
" re.search('<caret>.*(' + x + ')' + y, 'foo')\n",
".*(missing)missing");
".*(missing_value)missing_value");
}
public void testPercentFormattingRegexpAutoInjection() {
@@ -135,7 +135,7 @@ public class PyRegexpTest extends PyTestCase {
"\n" +
"def f(x, y):\n" +
" re.search('<caret>.*%s-%d' % (x, y), 'foo')\n",
".*missing-missing");
".*missing_value-missing_value");
}
public void testNewStyleFormattingRegexpAutoInjection() {
@@ -143,7 +143,7 @@ public class PyRegexpTest extends PyTestCase {
"\n" +
"def f(x, y):\n" +
" re.search('<caret>.*{foo}-{}'.format(x, foo=y), 'foo')\n",
".*missing-missing");
".*missing_value-missing_value");
}
public void testNewStyleFormattingEndsWithConstant() {
@@ -151,7 +151,7 @@ public class PyRegexpTest extends PyTestCase {
"\n" +
"def f(**kwargs):" +
" re.search('<caret>(foo{bar}baz$)'.format(**kwargs), 'foo')\n",
"(foomissingbaz$)");
"(foomissing_valuebaz$)");
}
private void doTestInjectedText(@NotNull String text, @NotNull String expected) {