Search in sources :

Example 1 with StringNodeInfo

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;
}
Also used : ASTNode(com.intellij.lang.ASTNode) StringNodeInfo(com.jetbrains.python.psi.PyUtil.StringNodeInfo) PsiElement(com.intellij.psi.PsiElement) Nullable(org.jetbrains.annotations.Nullable)

Example 2 with StringNodeInfo

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;
}
Also used : StringNodeInfo(com.jetbrains.python.psi.PyUtil.StringNodeInfo)

Example 3 with StringNodeInfo

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);
        }
    }
}
Also used : StringNodeInfo(com.jetbrains.python.psi.PyUtil.StringNodeInfo) PsiElement(com.intellij.psi.PsiElement)

Example 4 with StringNodeInfo

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);
            }
        }
    }
}
Also used : FStringParser(com.jetbrains.python.codeInsight.fstrings.FStringParser) ASTNode(com.intellij.lang.ASTNode) TextRange(com.intellij.openapi.util.TextRange) StringNodeInfo(com.jetbrains.python.psi.PyUtil.StringNodeInfo) Fragment(com.jetbrains.python.codeInsight.fstrings.FStringParser.Fragment)

Aggregations

StringNodeInfo (com.jetbrains.python.psi.PyUtil.StringNodeInfo)4 ASTNode (com.intellij.lang.ASTNode)2 PsiElement (com.intellij.psi.PsiElement)2 TextRange (com.intellij.openapi.util.TextRange)1 FStringParser (com.jetbrains.python.codeInsight.fstrings.FStringParser)1 Fragment (com.jetbrains.python.codeInsight.fstrings.FStringParser.Fragment)1 Nullable (org.jetbrains.annotations.Nullable)1