mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
ExpressionUtils#findStringLiteralRange: text blocks support
GitOrigin-RevId: b85c4c6e3968196f137010d9273458654ac7fbea
This commit is contained in:
committed by
intellij-monorepo-bot
parent
68baa03505
commit
9388320e76
@@ -1,12 +1,14 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiJavaToken;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -399,4 +401,152 @@ public class PsiLiteralUtil {
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the substring range inside Java String literal value back into the source code range.
|
||||
*
|
||||
* @param text string literal as present in source code (including quotes)
|
||||
* @param from start offset inside the represented string
|
||||
* @param to end offset inside the represented string
|
||||
* @return the range which represents the corresponding substring inside source representation,
|
||||
* or null if from/to values are out of bounds.
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange mapBackStringRange(@NotNull String text, int from, int to) {
|
||||
if (from > to || to < 0) return null;
|
||||
if (text.length() < 2 || !text.startsWith("\"") || !text.endsWith("\"")) {
|
||||
return null;
|
||||
}
|
||||
if (text.indexOf('\\') == -1) {
|
||||
return new TextRange(from + 1, to + 1);
|
||||
}
|
||||
text = text.substring(1, text.length() - 1);
|
||||
int charsSoFar = 0;
|
||||
int mappedFrom = -1;
|
||||
for (int i = 0; i != -1; i = getCharEndIndex(text, i)) {
|
||||
if (charsSoFar == from) {
|
||||
mappedFrom = i;
|
||||
}
|
||||
if (charsSoFar == to) {
|
||||
// +1 to count open quote
|
||||
return new TextRange(mappedFrom + 1, i + 1);
|
||||
}
|
||||
charsSoFar++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the substring range inside Java Text Block literal value back into the source code range.
|
||||
*
|
||||
* @param indent text block indent
|
||||
* @return range in source code representation, null when from/to out of bounds or given text block source code representation is invalid
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange mapBackTextBlockRange(@NotNull String text, int from, int to, int indent) {
|
||||
if (from > to || to < 0) return null;
|
||||
TextBlockModel model = TextBlockModel.create(text, indent);
|
||||
if (model == null) return null;
|
||||
return model.mapTextBlockRangeBack(from, to);
|
||||
}
|
||||
|
||||
private static int getCharEndIndex(@NotNull String line, int i) {
|
||||
if (i >= line.length()) return -1;
|
||||
char c = line.charAt(i++);
|
||||
if (c == '\\') {
|
||||
// like \u0020
|
||||
char c1 = line.charAt(i++);
|
||||
if (c1 == 'u') {
|
||||
while (i < line.length() && line.charAt(i) == 'u') i++;
|
||||
i += 4;
|
||||
} else if (c1 >= '0' && c1 <= '7') { // octal escape
|
||||
char c2 = i < line.length() ? line.charAt(i) : 0;
|
||||
if (c2 >= '0' && c2 <= '7') {
|
||||
i++;
|
||||
char c3 = i < line.length() ? line.charAt(i) : 0;
|
||||
if (c3 >= '0' && c3 <= '7' && c1 <= '3') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static class TextBlockModel {
|
||||
|
||||
private final String[] lines;
|
||||
private final int indent;
|
||||
private final int startPrefixLength;
|
||||
|
||||
private TextBlockModel(String[] lines, int indent, int startPrefixLength) {
|
||||
this.lines = lines;
|
||||
this.indent = indent;
|
||||
this.startPrefixLength = startPrefixLength;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TextRange mapTextBlockRangeBack(int from, int to) {
|
||||
int curOffset = startPrefixLength;
|
||||
int charsSoFar = 0;
|
||||
int mappedFrom = -1;
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
int linePrefixLength = findLinePrefixLength(line, indent);
|
||||
line = line.substring(linePrefixLength);
|
||||
boolean isLastLine = i == lines.length - 1;
|
||||
int lineSuffixLength = findLineSuffixLength(line, isLastLine);
|
||||
line = line.substring(0, line.length() - lineSuffixLength);
|
||||
if (!isLastLine) line += '\n';
|
||||
|
||||
curOffset += linePrefixLength;
|
||||
|
||||
int charIdx;
|
||||
int nextIdx = 0;
|
||||
while (true) {
|
||||
if (from == charsSoFar) {
|
||||
mappedFrom = curOffset + nextIdx;
|
||||
}
|
||||
if (to == charsSoFar) {
|
||||
return new TextRange(mappedFrom, curOffset + nextIdx);
|
||||
}
|
||||
charIdx = nextIdx;
|
||||
nextIdx = getCharEndIndex(line, charIdx);
|
||||
if (nextIdx == -1) break;
|
||||
charsSoFar++;
|
||||
if (nextIdx == line.length()) curOffset += lineSuffixLength;
|
||||
}
|
||||
curOffset += line.length();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int findLinePrefixLength(@NotNull String line, int indent) {
|
||||
boolean isBlankLine = line.chars().allMatch(Character::isWhitespace);
|
||||
return isBlankLine ? line.length() : indent;
|
||||
}
|
||||
|
||||
private static int findLineSuffixLength(@NotNull String line, boolean isLastLine) {
|
||||
if (isLastLine) return 0;
|
||||
int lastIdx = line.length() - 1;
|
||||
for (int i = lastIdx; i >= 0; i--) if (line.charAt(i) != ' ') return lastIdx - i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TextBlockModel create(@NotNull String text, int indent) {
|
||||
if (text.length() < 7 || !text.startsWith("\"\"\"") || !text.endsWith("\"\"\"")) return null;
|
||||
int startPrefixLength = findStartPrefixLength(text);
|
||||
if (startPrefixLength == -1) return null;
|
||||
String[] lines = text.substring(startPrefixLength, text.length() - 3).split("\n", -1);
|
||||
return new TextBlockModel(lines, indent, startPrefixLength);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private static int findStartPrefixLength(@NotNull String text) {
|
||||
int lineBreakIdx = text.indexOf("\n");
|
||||
if (lineBreakIdx == -1) return -1;
|
||||
return lineBreakIdx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiElementFactory;
|
||||
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
|
||||
import com.intellij.testFramework.LightPlatformCodeInsightTestCase;
|
||||
|
||||
public class MapBackTextBlockTextRangeTest extends LightPlatformCodeInsightTestCase {
|
||||
|
||||
public void testEmpty() {
|
||||
doTest("\"\"\"\n\"\"\"", 0, 0, new TextRange(4, 4));
|
||||
}
|
||||
|
||||
public void testExtraCharsBeforeContent() {
|
||||
doTest("\"\"\" \n\"\"\"", 0, 0, new TextRange(7, 7));
|
||||
}
|
||||
|
||||
public void testOneLineContentNoTrailingLine() {
|
||||
doTest("\"\"\"\n foo \"\"\"", 0, 5, new TextRange(7, 12));
|
||||
}
|
||||
|
||||
public void testOneLineContentTrailingLine() {
|
||||
doTest("\"\"\"\nfoo \n \"\"\"", 0, 3, new TextRange(4, 7));
|
||||
}
|
||||
|
||||
public void testTrailingLineWithContent() {
|
||||
doTest("\"\"\"\nfoo \n b\"\"\"", 0, 6, new TextRange(4, 12));
|
||||
}
|
||||
|
||||
public void testContentWithBlankLines() {
|
||||
doTest("\"\"\"\n" +
|
||||
" \n" +
|
||||
" foo \n" +
|
||||
" \n" +
|
||||
" \"\"\"", 0, 6, new TextRange(9, 22));
|
||||
}
|
||||
|
||||
public void testIndentNonZero() {
|
||||
doTest("\"\"\"\n foo\n bar\n \"\"\"", 0, 8, new TextRange(5, 14));
|
||||
}
|
||||
|
||||
public void testContentWithEscapeSequences() {
|
||||
doTest("\"\"\"\n\\u005c\\u005c\\u005c\\u005c\"\"\"", 1, 2, new TextRange(10, 16));
|
||||
}
|
||||
|
||||
public void testInvalidRange() {
|
||||
doTest("\"\"\"\n\"\"\"", 0, 1, null);
|
||||
}
|
||||
|
||||
private void doTest(String blockText, int from, int to, TextRange expectedRange) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(getProject());
|
||||
PsiLiteralExpressionImpl textBlock = (PsiLiteralExpressionImpl)factory.createExpressionFromText(blockText, null);
|
||||
assertSame(JavaTokenType.TEXT_BLOCK_LITERAL, textBlock.getLiteralElementType());
|
||||
int indent = textBlock.getTextBlockIndent();
|
||||
assertTrue(indent >= 0);
|
||||
TextRange actualRange = PsiLiteralUtil.mapBackTextBlockRange(textBlock.getText(), from, to, indent);
|
||||
assertEquals(expectedRange, actualRange);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.intellij.codeInsight;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -227,66 +226,4 @@ public abstract class CodeInsightUtilCore extends FileModificationService {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the substring range inside Java String literal value back into the source code range.
|
||||
*
|
||||
* @param text string literal as present in source code (including quotes)
|
||||
* @param from start offset inside the represented string
|
||||
* @param to end offset inside the represented string
|
||||
* @return the range which represents the corresponding substring inside source representation,
|
||||
* or null if from/to values are out of bounds.
|
||||
*/
|
||||
@Nullable
|
||||
public static TextRange mapBackStringRange(@NotNull String text, int from, int to) {
|
||||
if (from > to || to < 0) return null;
|
||||
if (text.startsWith("`")) {
|
||||
// raw string
|
||||
return new TextRange(from + 1, to + 1);
|
||||
}
|
||||
if (!text.startsWith("\"")) {
|
||||
return null;
|
||||
}
|
||||
if (text.indexOf('\\') == -1) {
|
||||
return new TextRange(from + 1, to + 1);
|
||||
}
|
||||
int curOffset = 0;
|
||||
int mappedFrom = -1, mappedTo = -1;
|
||||
int end = text.length() - 1;
|
||||
int i = 1;
|
||||
while (i <= end) {
|
||||
if (curOffset == from) {
|
||||
mappedFrom = i;
|
||||
}
|
||||
if (curOffset == to) {
|
||||
mappedTo = i;
|
||||
break;
|
||||
}
|
||||
if (i == end) break;
|
||||
char c = text.charAt(i++);
|
||||
if (c == '\\') {
|
||||
if (i == end) return null;
|
||||
// like \u0020
|
||||
char c1 = text.charAt(i++);
|
||||
if (c1 == 'u') {
|
||||
while (i < end && text.charAt(i) == 'u') i++;
|
||||
i += 4;
|
||||
} else if (c1 >= '0' && c1 <= '7') { // octal escape
|
||||
char c2 = i < end ? text.charAt(i) : 0;
|
||||
if (c2 >= '0' && c2 <= '7') {
|
||||
i++;
|
||||
char c3 = i < end ? text.charAt(i) : 0;
|
||||
if (c3 >= '0' && c3 <= '7' && c1 <= '3') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
curOffset++;
|
||||
}
|
||||
if (mappedFrom >= 0 && mappedTo >= 0) {
|
||||
return new TextRange(mappedFrom, mappedTo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
package com.siyeh.ig.psiutils;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
@@ -1271,10 +1271,17 @@ public class ExpressionUtils {
|
||||
public static TextRange findStringLiteralRange(PsiExpression expression, int from, int to) {
|
||||
if (to < 0 || from > to) return null;
|
||||
if (expression == null || !TypeUtils.isJavaLangString(expression.getType())) return null;
|
||||
if (expression instanceof PsiLiteralExpression) {
|
||||
String value = tryCast(((PsiLiteralExpression)expression).getValue(), String.class);
|
||||
if (expression instanceof PsiLiteralExpressionImpl) {
|
||||
PsiLiteralExpressionImpl literalExpression = (PsiLiteralExpressionImpl) expression;
|
||||
String value = tryCast(literalExpression.getValue(), String.class);
|
||||
if (value == null || value.length() < from || value.length() < to) return null;
|
||||
return CodeInsightUtilCore.mapBackStringRange(expression.getText(), from, to);
|
||||
String text = expression.getText();
|
||||
if (literalExpression.getLiteralElementType() == JavaTokenType.TEXT_BLOCK_LITERAL) {
|
||||
int indent = literalExpression.getTextBlockIndent();
|
||||
if (indent == -1) return null;
|
||||
return PsiLiteralUtil.mapBackTextBlockRange(text, from, to, indent);
|
||||
}
|
||||
return PsiLiteralUtil.mapBackStringRange(expression.getText(), from, to);
|
||||
}
|
||||
if (expression instanceof PsiParenthesizedExpression) {
|
||||
PsiExpression operand = ((PsiParenthesizedExpression)expression).getExpression();
|
||||
|
||||
Reference in New Issue
Block a user