mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-31442 Remove the rest of old f-strings implementation
This commit is contained in:
@@ -1,251 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.psi.PyStringLiteralUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class FStringParser {
|
||||
private final String myNodeText;
|
||||
private final TextRange myNodeContentRange;
|
||||
private final List<Fragment> myFragments = new ArrayList<>();
|
||||
private final List<Integer> mySingleRightBraces = new ArrayList<>();
|
||||
|
||||
@NotNull
|
||||
public static ParseResult parse(@NotNull String nodeText) {
|
||||
final FStringParser parser = new FStringParser(nodeText);
|
||||
parser.parseTopLevel();
|
||||
return new ParseResult(parser.mySingleRightBraces, parser.myFragments);
|
||||
}
|
||||
|
||||
private FStringParser(@NotNull String nodeText) {
|
||||
myNodeText = nodeText;
|
||||
myNodeContentRange = PyStringLiteralUtil.getContentRange(myNodeText);
|
||||
}
|
||||
|
||||
private void parseTopLevel() {
|
||||
int offset = myNodeContentRange.getStartOffset();
|
||||
while (offset < myNodeContentRange.getEndOffset()) {
|
||||
// First, skip named unicode escapes like "\N{LATIN SMALL LETTER A}" wherever they are
|
||||
final int nextOffset = skipNamedUnicodeEscape(offset);
|
||||
if (offset != nextOffset) {
|
||||
offset = nextOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
final char c1 = myNodeText.charAt(offset);
|
||||
final char c2 = offset + 1 < myNodeContentRange.getEndOffset() ? myNodeText.charAt(offset + 1) : '\0';
|
||||
|
||||
if ((c1 == '{' && c2 == '{') || (c1 == '}' && c2 == '}')) {
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
else if (c1 == '{') {
|
||||
offset = parseFragment(offset, 1);
|
||||
continue;
|
||||
}
|
||||
// Will be marked as errors
|
||||
else if (c1 == '}') {
|
||||
mySingleRightBraces.add(offset);
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
private int parseFragment(int leftBraceOffset, int depth) {
|
||||
assert myNodeText.charAt(leftBraceOffset) == '{';
|
||||
|
||||
int contentEndOffset = -1;
|
||||
int rightBraceOffset = -1;
|
||||
|
||||
int bracesBalance = 0;
|
||||
char stringLiteralQuote = '\0';
|
||||
int quotesNum = 0;
|
||||
|
||||
// Used for f-strings validation
|
||||
boolean containsNamedUnicodeEscape = false;
|
||||
int firstHashOffset = -1;
|
||||
|
||||
int offset = leftBraceOffset + 1;
|
||||
while (offset < myNodeContentRange.getEndOffset()) {
|
||||
// Actually they aren't allowed inside expression fragments, but we skip them anyway to prevent injection errors
|
||||
final int nextOffset = skipNamedUnicodeEscape(offset);
|
||||
if (offset != nextOffset) {
|
||||
containsNamedUnicodeEscape = true;
|
||||
offset = nextOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
final char c1 = myNodeText.charAt(offset);
|
||||
final char c2 = offset + 1 < myNodeContentRange.getEndOffset() ? myNodeText.charAt(offset + 1) : '\0';
|
||||
final char c3 = offset + 2 < myNodeContentRange.getEndOffset() ? myNodeText.charAt(offset + 2) : '\0';
|
||||
if (contentEndOffset == -1) {
|
||||
if (stringLiteralQuote != '\0') {
|
||||
if (c1 == '\'' || c1 == '"') {
|
||||
final int size = c2 == c1 && c3 == c1 ? 3 : 1;
|
||||
if (stringLiteralQuote == c1 && size == quotesNum) {
|
||||
stringLiteralQuote = '\0';
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (c1 == '\\') {
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (c1 == '\'' || c1 == '"') {
|
||||
quotesNum = c2 == c1 && c3 == c1 ? 3 : 1;
|
||||
stringLiteralQuote = c1;
|
||||
offset += quotesNum;
|
||||
continue;
|
||||
}
|
||||
else if (c1 == '#' && firstHashOffset == -1) {
|
||||
firstHashOffset = offset;
|
||||
}
|
||||
else if (c1 == '{' || c1 == '[' || c1 == '(') {
|
||||
bracesBalance++;
|
||||
}
|
||||
else if (bracesBalance > 0 && (c1 == '}' || c1 == ']' || c1 == ')')) {
|
||||
bracesBalance--;
|
||||
}
|
||||
else if (bracesBalance == 0 && (c1 == '}' || (c1 == '!' && c2 != '=') || c1 == ':')) {
|
||||
contentEndOffset = offset;
|
||||
if (c1 == '}') {
|
||||
rightBraceOffset = offset;
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c1 == '{') {
|
||||
offset = parseFragment(offset, depth + 1);
|
||||
continue;
|
||||
}
|
||||
else if (c1 == '}') {
|
||||
rightBraceOffset = offset;
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
if (contentEndOffset == -1) {
|
||||
contentEndOffset = offset;
|
||||
}
|
||||
myFragments.add(new Fragment(leftBraceOffset,
|
||||
contentEndOffset,
|
||||
rightBraceOffset,
|
||||
containsNamedUnicodeEscape,
|
||||
firstHashOffset,
|
||||
depth));
|
||||
return offset;
|
||||
}
|
||||
|
||||
private int skipNamedUnicodeEscape(int offset) {
|
||||
if (StringUtil.startsWith(myNodeText, offset, "\\N{")) {
|
||||
final int rightBraceOffset = myNodeText.indexOf('}', offset + 3);
|
||||
return rightBraceOffset < 0 ? myNodeContentRange.getEndOffset() : rightBraceOffset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static class Fragment {
|
||||
private final int myLeftBraceOffset;
|
||||
private final int myRightBraceOffset;
|
||||
private final int myContentEndOffset;
|
||||
private final boolean myContainsNamedUnicodeEscape;
|
||||
private final int myFirstHashOffset;
|
||||
private final int myDepth;
|
||||
|
||||
private Fragment(int leftBraceOffset,
|
||||
int contentEndOffset,
|
||||
int rightBraceOffset,
|
||||
boolean containsUnicodeEscape,
|
||||
int firstHashOffset,
|
||||
int depth) {
|
||||
assert leftBraceOffset < contentEndOffset;
|
||||
assert rightBraceOffset < 0 || contentEndOffset <= rightBraceOffset;
|
||||
assert firstHashOffset < 0 || leftBraceOffset < firstHashOffset && firstHashOffset < contentEndOffset;
|
||||
|
||||
myLeftBraceOffset = leftBraceOffset;
|
||||
myRightBraceOffset = rightBraceOffset;
|
||||
myContentEndOffset = contentEndOffset;
|
||||
myContainsNamedUnicodeEscape = containsUnicodeEscape;
|
||||
myFirstHashOffset = firstHashOffset;
|
||||
myDepth = depth;
|
||||
}
|
||||
|
||||
public int getLeftBraceOffset() {
|
||||
return myLeftBraceOffset;
|
||||
}
|
||||
|
||||
public int getRightBraceOffset() {
|
||||
return myRightBraceOffset;
|
||||
}
|
||||
|
||||
public int getContentEndOffset() {
|
||||
return myContentEndOffset;
|
||||
}
|
||||
|
||||
public boolean containsNamedUnicodeEscape() {
|
||||
return myContainsNamedUnicodeEscape;
|
||||
}
|
||||
|
||||
public int getFirstHashOffset() {
|
||||
return myFirstHashOffset;
|
||||
}
|
||||
|
||||
public int getDepth() {
|
||||
return myDepth;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TextRange getContentRange() {
|
||||
return TextRange.create(myLeftBraceOffset + 1, myContentEndOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParseResult {
|
||||
private final List<Integer> mySingleRightBraces;
|
||||
private final List<Fragment> myFragments;
|
||||
|
||||
private ParseResult(@NotNull List<Integer> singleRightBraces, @NotNull List<Fragment> fragments) {
|
||||
mySingleRightBraces = singleRightBraces;
|
||||
myFragments = ContainerUtil.sorted(fragments, Comparator.comparingInt(Fragment::getLeftBraceOffset));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Integer> getSingleRightBraces() {
|
||||
return Collections.unmodifiableList(mySingleRightBraces);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Fragment> getFragments() {
|
||||
return Collections.unmodifiableList(myFragments);
|
||||
}
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyRecursiveElementVisitor;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PyRecursiveElementVisitor} that recursively visits Python files injected into f-strings.
|
||||
* You can check whether you are inside injected fragment using {@link #myContainingFString} field.
|
||||
*/
|
||||
public class PyFStringAwareRecursiveVisitor extends PyRecursiveElementVisitor {
|
||||
protected PyStringLiteralExpression myContainingFString;
|
||||
|
||||
@Override
|
||||
public void visitPyStringLiteralExpression(PyStringLiteralExpression pyString) {
|
||||
final List<TextRange> formatNodeRanges = ContainerUtil.mapNotNull(pyString.getStringNodes(), node -> {
|
||||
final PyUtil.StringNodeInfo nodeInfo = new PyUtil.StringNodeInfo(node);
|
||||
return nodeInfo.isFormatted() ? nodeInfo.getAbsoluteContentRange().shiftRight(-pyString.getTextOffset()) : null;
|
||||
});
|
||||
if (!formatNodeRanges.isEmpty()) {
|
||||
final InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(pyString.getProject());
|
||||
for (Pair<PsiElement, TextRange> pair : ContainerUtil.notNullize(injectionManager.getInjectedPsiFiles(pyString))) {
|
||||
final PyFile pyFile = as(pair.getFirst(), PyFile.class);
|
||||
if (pyFile != null && ContainerUtil.exists(formatNodeRanges, range -> range.contains(pair.getSecond()))) {
|
||||
myContainingFString = pyString;
|
||||
try {
|
||||
pyFile.accept(this);
|
||||
}
|
||||
finally {
|
||||
myContainingFString = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitPyStringLiteralExpression(pyString);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.fstrings.PyFStringAwareRecursiveVisitor;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionFromMethodQuickFix;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeMethodStaticQuickFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
@@ -93,7 +92,7 @@ public class PyMethodMayBeStaticInspection extends PyInspection {
|
||||
}
|
||||
|
||||
final boolean[] mayBeStatic = {true};
|
||||
PyRecursiveElementVisitor visitor = new PyFStringAwareRecursiveVisitor() {
|
||||
PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitPyRaiseStatement(PyRaiseStatement node) {
|
||||
super.visitPyRaiseStatement(node);
|
||||
|
||||
@@ -1,137 +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;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.codeInsight.fstrings.FStringParser;
|
||||
import com.jetbrains.python.codeInsight.fstrings.FStringParser.Fragment;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyFStringTest extends PyTestCase {
|
||||
|
||||
private static void doTestRanges(@NotNull @Language("Python") String fstringLiteral) {
|
||||
final Pair<String, List<Integer>> pair = extractOffsets(fstringLiteral);
|
||||
final List<Integer> barOffsets = pair.getSecond();
|
||||
assertTrue("Odd number of markers", barOffsets.size() % 2 == 0);
|
||||
final List<Fragment> fragments = FStringParser.parse(pair.getFirst()).getFragments();
|
||||
final List<TextRange> actualRanges = ContainerUtil.map(fragments, Fragment::getContentRange);
|
||||
final List<TextRange> expectedRanges = new ArrayList<>();
|
||||
for (int i = 0; i < barOffsets.size(); i += 2) {
|
||||
expectedRanges.add(TextRange.create(barOffsets.get(i), barOffsets.get(i + 1)));
|
||||
}
|
||||
assertSameElements(actualRanges, expectedRanges);
|
||||
}
|
||||
|
||||
private static void doTestSingleRightBraces(@NotNull @Language("Python") String fstringLiteral) {
|
||||
final Pair<String, List<Integer>> pair = extractOffsets(fstringLiteral);
|
||||
assertEquals(pair.getSecond(), FStringParser.parse(pair.getFirst()).getSingleRightBraces());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<String, List<Integer>> extractOffsets(@NotNull String fstringLiteral) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
int lastBarOffset = -1;
|
||||
final List<Integer> offsets = new ArrayList<>();
|
||||
while (true) {
|
||||
final int nextOffset = fstringLiteral.indexOf("|", lastBarOffset + 1);
|
||||
if (nextOffset < 0) {
|
||||
builder.append(fstringLiteral.substring(lastBarOffset + 1));
|
||||
break;
|
||||
}
|
||||
builder.append(fstringLiteral, lastBarOffset + 1, nextOffset);
|
||||
lastBarOffset = nextOffset;
|
||||
offsets.add(lastBarOffset - offsets.size());
|
||||
}
|
||||
return Pair.create(builder.toString(), offsets);
|
||||
}
|
||||
|
||||
public void testSimple() {
|
||||
doTestRanges("f'{|x|} {|y|} {|42|'");
|
||||
}
|
||||
|
||||
public void testEscapedBraces() {
|
||||
doTestRanges("f'{{{|x|}}}{{}}{{{|x|}'");
|
||||
}
|
||||
|
||||
public void testBracesInside() {
|
||||
doTestRanges("f'{| {1, 2, 3} |} {| {x for x in range(10)} |}'");
|
||||
}
|
||||
|
||||
public void testBraceInsideNestedLiteral() {
|
||||
doTestRanges("f'{|\"{x}}}\"|}'");
|
||||
doTestRanges("f'{|d[\"}\"]|}'");
|
||||
doTestRanges("f'''{|\"'}\"|}'''");
|
||||
doTestRanges("f'''{|\"\\\"}\"|}'''");
|
||||
doTestRanges("f'''{|\"\"\"}'\"}'\"\"\"|}'''");
|
||||
doTestRanges("f\"{|'''}\\\"'\\\"'''|}\"");
|
||||
}
|
||||
|
||||
public void testChunkTypeConversionsAndFormatSpecifiers() {
|
||||
doTestRanges("f'{|x|!s} {|y|!r}}'");
|
||||
doTestRanges("f'{|x|:^42}}'");
|
||||
doTestRanges("f'{|x|!s:{|y|}.{|z|}}'");
|
||||
doTestRanges("f'{|x|:{|sum({x for x in range(10)})|}}'");
|
||||
}
|
||||
|
||||
public void testNestedLambda() {
|
||||
doTestRanges("f'{|(lambda: x)|}'");
|
||||
}
|
||||
|
||||
public void testNotEquals() {
|
||||
doTestRanges("f'{|x != 42|}'");
|
||||
}
|
||||
|
||||
// PY-20785
|
||||
public void testNamedUnicodeEscapes() {
|
||||
doTestRanges("f'\\N{foo}\\N{}\\N{{{{{}{|42 + \\N{DIGIT ONE}|}'");
|
||||
doTestRanges("f'{|x|:\\N{DIGIT_ONE}}'");
|
||||
}
|
||||
|
||||
// PY-20785
|
||||
public void testUnicodeEscapeInsideExpressionFragment() {
|
||||
doTestUnicodeEscapeDetection("f'{\\N{FOO}}'", true);
|
||||
doTestUnicodeEscapeDetection("f'{\"\\N{FOO\"}'", true);
|
||||
doTestUnicodeEscapeDetection("f'{\"\\N{\"}'", true);
|
||||
doTestUnicodeEscapeDetection("f'{\"\\\\N{FOO}\"}'", false);
|
||||
}
|
||||
|
||||
private static void doTestUnicodeEscapeDetection(String fStringText, boolean expected) {
|
||||
final List<Fragment> fragments = FStringParser.parse(fStringText).getFragments();
|
||||
assertSize(1, fragments);
|
||||
final Fragment offsets = fragments.get(0);
|
||||
assertEquals(expected, offsets.containsNamedUnicodeEscape());
|
||||
}
|
||||
|
||||
// PY-20897
|
||||
public void testSingleRightBraces() {
|
||||
doTestSingleRightBraces("f'|}'");
|
||||
doTestSingleRightBraces("f'{x}{y}|}'");
|
||||
doTestSingleRightBraces("f'{x}}}|}'");
|
||||
doTestSingleRightBraces("f'{x:{y}}|}'");
|
||||
doTestSingleRightBraces("f'{x:{y}}}}'");
|
||||
doTestSingleRightBraces("f'{\"}\":{\"}\"}}'");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user