PY-31442 Use PyStringElement API in PyInjectionUtil, finally remove PyFStringsInjector

This commit is contained in:
Mikhail Golubev
2018-10-01 12:46:48 +03:00
parent 3c4ae7d90e
commit e44dd87695
4 changed files with 17 additions and 136 deletions
@@ -15,7 +15,6 @@
*/
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;
@@ -23,8 +22,6 @@ import com.intellij.psi.PsiComment;
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;
@@ -32,7 +29,6 @@ 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.*;
@@ -43,18 +39,14 @@ import static com.jetbrains.python.inspections.PyStringFormatParser.*;
public class PyInjectionUtil {
public static class InjectionResult {
public static final InjectionResult EMPTY = new InjectionResult(false, true, Collections.emptyList());
public static final InjectionResult EMPTY = new InjectionResult(false, true);
private final boolean myInjected;
private final boolean myStrict;
private final List<PyStringLiteralExpression> myCollectedFStrings;
public InjectionResult(boolean injected, boolean strict) {this(injected, strict, Collections.emptyList());}
private InjectionResult(boolean injected, boolean strict, @NotNull List<PyStringLiteralExpression> nodes) {
public InjectionResult(boolean injected, boolean strict) {
myInjected = injected;
myStrict = strict;
myCollectedFStrings = nodes;
}
public boolean isInjected() {
@@ -66,9 +58,7 @@ public class PyInjectionUtil {
}
public InjectionResult append(@NotNull InjectionResult result) {
return new InjectionResult(myInjected || result.isInjected(),
myStrict && result.isStrict(),
ContainerUtil.concat(myCollectedFStrings, result.myCollectedFStrings));
return new InjectionResult(myInjected || result.isInjected(), myStrict && result.isStrict());
}
}
@@ -104,12 +94,6 @@ public class PyInjectionUtil {
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;
}
@@ -165,29 +149,23 @@ 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);
final TextRange contentRange = nodeInfo.getContentRange();
for (PyStringElement stringElem : expr.getStringElements()) {
final int nodeOffsetInParent = stringElem.getTextOffset() - expr.getTextRange().getStartOffset();
final TextRange contentRange = stringElem.getContentRange();
final int contentStartOffset = contentRange.getStartOffset();
if (formatting != Formatting.NONE || nodeInfo.isFormatted()) {
// Each range is relative to the start of the string node
if (formatting != Formatting.NONE || stringElem.isFormatted()) {
// Each range is relative to the start of the string element
final List<TextRange> subsRanges;
if (formatting != Formatting.NONE) {
final String content = nodeInfo.getContent();
final String content = stringElem.getContent();
subsRanges = StreamEx.of(formatting == Formatting.NEW_STYLE ? parseNewStyleFormat(content) : parsePercentFormat(content))
.select(SubstitutionChunk.class)
.map(chunk -> chunk.getTextRange().shiftRight(contentStartOffset))
.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}}
.map(f -> TextRange.create(f.getLeftBraceOffset(),
Math.max(f.getRightBraceOffset() + 1, f.getContentEndOffset())))
subsRanges = StreamEx.of(((PyFormattedStringElement)stringElem).getFragments())
.map(f -> f.getTextRange().shiftLeft(stringElem.getTextOffset()))
.toList();
}
if (!subsRanges.isEmpty()) {
@@ -238,7 +216,7 @@ public class PyInjectionUtil {
injected = true;
}
}
return new InjectionResult(injected, strict, hasFormattedNodes ? Collections.singletonList(expr) : Collections.emptyList());
return new InjectionResult(injected, strict);
}
else if (element instanceof PyParenthesizedExpression) {
final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression();
@@ -1,96 +0,0 @@
/*
* Copyright 2000-2016 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 com.jetbrains.python.codeInsight.fstrings;
import com.intellij.codeInsight.generation.CommentByLineCommentHandler;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.lang.injection.MultiHostRegistrar;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.codeInsight.PyInjectorBase;
import com.jetbrains.python.codeInsight.fstrings.FStringParser.Fragment;
import com.jetbrains.python.documentation.doctest.PyDocstringLanguageDialect;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.PyUtil.StringNodeInfo;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* @author Mikhail Golubev
*/
public class PyFStringsInjector extends PyInjectorBase {
@Override
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
final PyStringLiteralExpression pyString = as(context, PyStringLiteralExpression.class);
if (pyString == null) {
return;
}
injectFStringFragments(registrar, pyString);
}
public static void injectFStringFragments(@NotNull MultiHostRegistrar registrar, @NotNull PyStringLiteralExpression pyString) {
final PyDocstringLanguageDialect docstringLanguage = PyDocstringLanguageDialect.getInstance();
for (ASTNode node : pyString.getStringNodes()) {
final int relNodeOffset = node.getTextRange().getStartOffset() - pyString.getTextRange().getStartOffset();
for (Fragment offsets : getInjectionRanges(node)) {
if (offsets.containsNamedUnicodeEscape()) continue;
registrar.startInjecting(docstringLanguage);
registrar.addPlace(null, null, pyString, offsets.getContentRange().shiftRight(relNodeOffset));
registrar.doneInjecting();
}
}
disableCommentingInFragments(pyString);
}
private static void disableCommentingInFragments(@NotNull PyStringLiteralExpression pyString) {
final Project project = pyString.getProject();
final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(project);
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final PyDocstringLanguageDialect docstringLanguage = PyDocstringLanguageDialect.getInstance();
StreamEx.of(injectedLanguageManager.getCachedInjectedDocumentsInRange(pyString.getContainingFile(), pyString.getTextRange()))
.map(window -> documentManager.getPsiFile(window))
.nonNull()
.filter(file -> file.getLanguage().isKindOf(docstringLanguage))
.forEach(CommentByLineCommentHandler::markInjectedFileUnsuitableForLineComment);
}
@NotNull
private static List<Fragment> getInjectionRanges(@NotNull ASTNode node) {
final StringNodeInfo nodeInfo = new StringNodeInfo(node);
if (nodeInfo.isFormatted()) {
return FStringParser.parse(node.getText()).getFragments();
}
return Collections.emptyList();
}
@Nullable
@Override
public Language getInjectedLanguage(@NotNull PsiElement context) {
return context instanceof PyStringLiteralExpression? PyDocstringLanguageDialect.getInstance() : null;
}
}
@@ -649,4 +649,9 @@ public class Py3ResolveTest extends PyResolveTestCase {
public void testFStringInsideAssertStatement() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> assertResolvesTo(PyParameter.class, "name"));
}
// PY-21493
public void testRegexpAndFStringCombined() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> assertResolvesTo(PyTargetExpression.class, "foo"));
}
}
@@ -24,7 +24,6 @@ import com.jetbrains.python.fixtures.PyResolveTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyReferenceOwner;
import com.jetbrains.python.psi.PyTargetExpression;
/**
* @author Mikhail Golubev
@@ -75,9 +74,4 @@ public class PyInjectionResolveTest extends PyResolveTestCase {
public void testFunctionTypeCommentReturnTypeReference() {
assertResolvesTo(PyClass.class, "MyClass");
}
// PY-21493
public void testRegexpAndFStringCombined() {
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> assertResolvesTo(PyTargetExpression.class, "foo"));
}
}