PY-21493 PY-23270 Python IntelliLang injectors are aware of f-strings

and considered before the dedicated injector for f-strings takes place.
Also, PyInjectionUtil.registerStringLiteralInjection now takes care of
f-string injections itself.
This commit is contained in:
Mikhail Golubev
2018-07-16 22:38:21 +03:00
parent 6caccc8e4d
commit 1121d314bb
6 changed files with 41 additions and 29 deletions
@@ -7,7 +7,8 @@
</extensions>
<extensions defaultExtensionNs="com.intellij">
<patterns.patternClass className="com.jetbrains.python.patterns.PythonPatterns" alias="py"/>
<multiHostInjector implementation="com.jetbrains.python.intelliLang.PyConfigurationInjector"/>
<multiHostInjector implementation="com.jetbrains.python.intelliLang.PyTemporaryInjector"/>
<!-- These must be registered before PyFStringsInjector, otherwise it won't allow to inject anything else inside f-strings -->
<multiHostInjector implementation="com.jetbrains.python.intelliLang.PyConfigurationInjector" order="before f-strings"/>
<multiHostInjector implementation="com.jetbrains.python.intelliLang.PyTemporaryInjector" order="before f-strings"/>
</extensions>
</idea-plugin>
+1 -1
View File
@@ -633,7 +633,7 @@
serviceImplementation="com.jetbrains.python.PythonFoldingSettings"/>
<!-- f-strings -->
<multiHostInjector implementation="com.jetbrains.python.codeInsight.fstrings.PyFStringsInjector"/>
<multiHostInjector implementation="com.jetbrains.python.codeInsight.fstrings.PyFStringsInjector" id="f-strings"/>
<!-- postfix templates!-->
<codeInsight.template.postfixTemplateProvider language="Python"
@@ -16,6 +16,7 @@
package com.jetbrains.python.codeInsight;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
@@ -23,6 +24,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.fstrings.FStringParser;
import com.jetbrains.python.codeInsight.fstrings.PyFStringsInjector;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyCallExpressionNavigator;
import one.util.streamex.StreamEx;
@@ -30,6 +32,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.python.inspections.PyStringFormatParser.*;
@@ -40,14 +43,18 @@ import static com.jetbrains.python.inspections.PyStringFormatParser.*;
public class PyInjectionUtil {
public static class InjectionResult {
public static InjectionResult EMPTY = new InjectionResult(false, true);
public static final InjectionResult EMPTY = new InjectionResult(false, true, Collections.emptyList());
private final boolean myInjected;
private final boolean myStrict;
private final List<PyStringLiteralExpression> myCollectedFStrings;
public InjectionResult(boolean injected, boolean strict) {
public InjectionResult(boolean injected, boolean strict) {this(injected, strict, Collections.emptyList());}
private InjectionResult(boolean injected, boolean strict, @NotNull List<PyStringLiteralExpression> nodes) {
myInjected = injected;
myStrict = strict;
myCollectedFStrings = nodes;
}
public boolean isInjected() {
@@ -59,7 +66,9 @@ public class PyInjectionUtil {
}
public InjectionResult append(@NotNull InjectionResult result) {
return new InjectionResult(myInjected || result.isInjected(), myStrict && result.isStrict());
return new InjectionResult(myInjected || result.isInjected(),
myStrict && result.isStrict(),
ContainerUtil.concat(myCollectedFStrings, result.myCollectedFStrings));
}
}
@@ -87,8 +96,21 @@ public class PyInjectionUtil {
* string concatenations or formatting.
*/
@NotNull
public static InjectionResult registerStringLiteralInjection(@NotNull PsiElement element, @NotNull MultiHostRegistrar registrar) {
return processStringLiteral(element, registrar, "", "", Formatting.NONE);
public static InjectionResult registerStringLiteralInjection(@NotNull PsiElement element,
@NotNull MultiHostRegistrar registrar,
@NotNull Language language) {
registrar.startInjecting(language);
final InjectionResult result = processStringLiteral(element, registrar, "", "", Formatting.NONE);
if (result.isInjected()) {
registrar.doneInjecting();
}
// Only one injector can process the given element, thus we should additionally
// take care of f-string here instead of PyFStringsInjector
for (PyStringLiteralExpression literal: result.myCollectedFStrings) {
PyFStringsInjector.injectFStringFragments(registrar, literal);
}
return result;
}
private static boolean isStringLiteralPart(@NotNull PsiElement element, @Nullable PsiElement context) {
@@ -143,6 +165,7 @@ public class PyInjectionUtil {
boolean injected = false;
boolean strict = true;
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
boolean hasFormattedNodes = false;
for (ASTNode node : expr.getStringNodes()) {
final int nodeOffsetInParent = node.getStartOffset() - expr.getTextRange().getStartOffset();
final PyUtil.StringNodeInfo nodeInfo = new PyUtil.StringNodeInfo(node);
@@ -159,6 +182,7 @@ public class PyInjectionUtil {
.toList();
}
else {
hasFormattedNodes = true;
// f-string fragment parser handles string literal prefix and opening quotes itself
subsRanges = StreamEx.of(FStringParser.parse(node.getText()).getFragments())
.filter(f -> f.getDepth() == 1) // don't consider nested fragments like {foo:{bar}}
@@ -214,7 +238,7 @@ public class PyInjectionUtil {
injected = true;
}
}
return new InjectionResult(injected, strict);
return new InjectionResult(injected, strict, hasFormattedNodes ? Collections.singletonList(expr) : Collections.emptyList());
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
@@ -47,12 +47,7 @@ public abstract class PyInjectorBase implements MultiHostInjector {
if (language != null) {
final PsiElement element = PyInjectionUtil.getLargestStringLiteral(context);
if (element != null) {
registrar.startInjecting(language);
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(element, registrar);
if (result.isInjected()) {
registrar.doneInjecting();
}
return result;
return PyInjectionUtil.registerStringLiteralInjection(element, registrar, language);
}
}
return PyInjectionUtil.InjectionResult.EMPTY;
@@ -46,7 +46,7 @@ public class PyFStringsInjector extends PyInjectorBase {
injectFStringFragments(registrar, pyString);
}
public static void injectFStringFragments(@NotNull MultiHostRegistrar registrar, PyStringLiteralExpression pyString) {
public static void injectFStringFragments(@NotNull MultiHostRegistrar registrar, @NotNull PyStringLiteralExpression pyString) {
for (ASTNode node : pyString.getStringNodes()) {
final int relNodeOffset = node.getTextRange().getStartOffset() - pyString.getTextRange().getStartOffset();
for (Fragment offsets : getInjectionRanges(node)) {
@@ -25,7 +25,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.codeInsight.PyInjectionUtil;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.fstrings.PyFStringsInjector;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.TypeEvalContext;
@@ -97,20 +96,13 @@ public class PythonRegexpInjector implements MultiHostInjector {
private static void injectRegexpLanguage(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context, boolean verbose) {
final Language language = verbose ? PythonVerboseRegexpLanguage.INSTANCE : PythonRegexpLanguage.INSTANCE;
registrar.startInjecting(language);
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(context, registrar);
if (result.isInjected()) {
registrar.doneInjecting();
if (!result.isStrict()) {
final PsiFile file = InjectedLanguageUtil.getCachedInjectedFileWithLanguage(context, language);
if (file != null) {
file.putUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
final PyInjectionUtil.InjectionResult result = PyInjectionUtil.registerStringLiteralInjection(context, registrar, language);
if (result.isInjected() && !result.isStrict()) {
final PsiFile file = InjectedLanguageUtil.getCachedInjectedFileWithLanguage(context, language);
if (file != null) {
file.putUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION, Boolean.TRUE);
}
}
if (context instanceof PyStringLiteralExpression) {
PyFStringsInjector.injectFStringFragments(registrar, (PyStringLiteralExpression)context);
}
}
@NotNull