use of com.jetbrains.python.psi.PyUtil.StringNodeInfo in project intellij-community by JetBrains.
the class BaseConvertToFStringProcessor method adjustQuotesInsideInjectedExpression.
@Nullable
protected PsiElement adjustQuotesInsideInjectedExpression(@NotNull PsiElement expression) {
final PsiElement copied = expression.copy();
final char hostQuote = myNodeInfo.getSingleQuote();
final PyElementGenerator generator = PyElementGenerator.getInstance(myPyString.getProject());
final Collection<PyStringLiteralExpression> innerStrings = PsiTreeUtil.collectElementsOfType(copied, PyStringLiteralExpression.class);
for (PyStringLiteralExpression literal : innerStrings) {
final List<ASTNode> nodes = literal.getStringNodes();
// TODO figure out what to do with those
if (nodes.size() > 1) {
return copied;
}
final StringNodeInfo info = new StringNodeInfo(nodes.get(0));
// Nest string contain the same type of quote as host string inside, and we cannot escape inside f-string -- retreat
final String content = info.getContent();
if (content.indexOf(hostQuote) >= 0) {
return null;
}
if (!info.isTerminated()) {
return null;
}
if (info.getQuote().startsWith(myNodeInfo.getQuote())) {
final char targetSingleQuote = PyStringLiteralUtil.flipQuote(hostQuote);
if (content.indexOf(targetSingleQuote) >= 0) {
return null;
}
final String targetQuote = info.getQuote().replace(hostQuote, targetSingleQuote);
final String stringWithSwappedQuotes = info.getPrefix() + targetQuote + content + targetQuote;
final PsiElement replaced = literal.replace(generator.createStringLiteralAlreadyEscaped(stringWithSwappedQuotes));
if (literal == copied) {
return replaced;
}
}
}
return copied;
}
use of com.jetbrains.python.psi.PyUtil.StringNodeInfo in project intellij-community by JetBrains.
the class PyConvertTripleQuotedStringIntention method isAvailable.
public boolean isAvailable(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (!(file instanceof PyFile)) {
return false;
}
final int caretOffset = editor.getCaretModel().getOffset();
final PyStringLiteralExpression pyString = PsiTreeUtil.getParentOfType(file.findElementAt(caretOffset), PyStringLiteralExpression.class);
if (pyString != null) {
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(pyString, PyDocStringOwner.class);
if (docStringOwner != null) {
if (docStringOwner.getDocStringExpression() == pyString)
return false;
}
for (StringNodeInfo info : extractStringNodesInfo(pyString)) {
if (info.isTripleQuoted() && info.isTerminated() && info.getNode().getTextRange().contains(caretOffset)) {
return true;
}
}
}
return false;
}
use of com.jetbrains.python.psi.PyUtil.StringNodeInfo in project intellij-community by JetBrains.
the class PyConvertTripleQuotedStringIntention method doInvoke.
public void doInvoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) throws IncorrectOperationException {
final PyStringLiteralExpression pyString = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class);
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
if (pyString != null) {
final StringBuilder result = new StringBuilder();
final List<StringNodeInfo> nodeInfos = extractStringNodesInfo(pyString);
for (int i = 0; i < nodeInfos.size(); i++) {
final StringNodeInfo info = nodeInfos.get(i);
List<String> lines = StringUtil.split(info.getContent(), "\n", true, false);
boolean lastLineExcluded = false;
if (lines.size() > 1 && lines.get(lines.size() - 1).isEmpty()) {
lastLineExcluded = true;
lines = lines.subList(0, lines.size() - 1);
}
final boolean inLastNode = i == nodeInfos.size() - 1;
for (int j = 0; j < lines.size(); j++) {
final String line = lines.get(j);
final boolean inLastLine = j == lines.size() - 1;
if (info.isRaw()) {
appendSplittedRawStringLine(result, info, line);
if (!inLastLine || lastLineExcluded) {
result.append(" ").append(info.getSingleQuote()).append("\\n").append(info.getSingleQuote());
}
} else {
result.append(info.getPrefix());
result.append(info.getSingleQuote());
result.append(convertToValidSubString(line, info.getSingleQuote(), info.isTripleQuoted()));
if (!inLastLine || lastLineExcluded) {
result.append("\\n");
}
result.append(info.getSingleQuote());
}
if (!(inLastNode && inLastLine)) {
result.append("\n");
}
}
}
if (result.indexOf("\n") >= 0) {
result.insert(0, "(");
result.append(")");
}
PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(pyString), result.toString());
final PsiElement parent = pyString.getParent();
if (expression instanceof PyParenthesizedExpression && (parent instanceof PyParenthesizedExpression || parent instanceof PyTupleExpression || parent instanceof PyArgumentList && ArrayUtil.getFirstElement(((PyArgumentList) parent).getArguments()) == pyString)) {
expression = ((PyParenthesizedExpression) expression).getContainedExpression();
}
if (expression != null) {
pyString.replace(expression);
}
}
}
use of com.jetbrains.python.psi.PyUtil.StringNodeInfo in project intellij-community by JetBrains.
the class FStringsAnnotator method visitPyStringLiteralExpression.
@Override
public void visitPyStringLiteralExpression(PyStringLiteralExpression pyString) {
for (ASTNode node : pyString.getStringNodes()) {
final StringNodeInfo nodeInfo = new StringNodeInfo(node);
final String nodeText = node.getText();
if (nodeInfo.isFormatted()) {
final int nodeContentEnd = nodeInfo.getContentRange().getEndOffset();
final FStringParser.ParseResult result = FStringParser.parse(nodeText);
TextRange unclosedBraceRange = null;
for (Fragment fragment : result.getFragments()) {
final int fragLeftBrace = fragment.getLeftBraceOffset();
final int fragContentEnd = fragment.getContentEndOffset();
final int fragRightBrace = fragment.getRightBraceOffset();
final TextRange wholeFragmentRange = TextRange.create(fragLeftBrace, fragRightBrace == -1 ? nodeContentEnd : fragRightBrace + 1);
if (fragment.getDepth() > 2) {
// Do not report anything about expression fragments nested deeper that three times
if (fragment.getDepth() == 3) {
report("Expression fragment inside f-string is nested too deeply", wholeFragmentRange, node);
}
continue;
}
if (CharArrayUtil.isEmptyOrSpaces(nodeText, fragLeftBrace + 1, fragContentEnd) && fragContentEnd < nodeContentEnd) {
final TextRange range = TextRange.create(fragLeftBrace, fragContentEnd + 1);
report("Empty expression fragments are not allowed inside f-strings", range, node);
}
if (fragRightBrace == -1 && unclosedBraceRange == null) {
unclosedBraceRange = wholeFragmentRange;
}
if (fragment.getFirstHashOffset() != -1) {
final TextRange range = TextRange.create(fragment.getFirstHashOffset(), fragment.getContentEndOffset());
report("Expression fragments inside f-strings cannot include line comments", range, node);
}
for (int i = fragLeftBrace + 1; i < fragment.getContentEndOffset(); i++) {
if (nodeText.charAt(i) == '\\') {
reportCharacter("Expression fragments inside f-strings cannot include backslashes", i, node);
}
}
// Do not warn about illegal conversion character if '!' is right before closing quotes
if (fragContentEnd < nodeContentEnd && nodeText.charAt(fragContentEnd) == '!' && fragContentEnd + 1 < nodeContentEnd) {
final char conversionChar = nodeText.charAt(fragContentEnd + 1);
// No conversion character -- highlight only "!"
if (fragContentEnd + 1 == fragRightBrace || conversionChar == ':') {
reportCharacter("Conversion character is expected: should be one of 's', 'r', 'a'", fragContentEnd, node);
} else // Wrong conversion character -- highlight both "!" and the following symbol
if ("sra".indexOf(conversionChar) < 0) {
final TextRange range = TextRange.from(fragContentEnd, 2);
report("Illegal conversion character '" + conversionChar + "': should be one of 's', 'r', 'a'", range, node);
}
}
}
for (Integer offset : result.getSingleRightBraces()) {
reportCharacter("Single '}' is not allowed inside f-strings", offset, node);
}
if (unclosedBraceRange != null) {
report("'}' is expected", unclosedBraceRange, node);
}
}
}
}
Aggregations