<commit_before>package com.axeldev;

import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.PhpExpressionCodeFragment;
import com.jetbrains.php.lang.psi.PhpFile;
import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import org.jetbrains.annotations.NotNull;

public class PhpReplaceQuotesWithConcatenationIntention extends PsiElementBaseIntentionAction {

    public static final String FAMILY_NAME = "Replace quotes";
    // TODO mention also variable concatenation, make title dynamic?
    public static final String INTENTION_NAME = "Replace quotes with unescaping and variable";
    public static final char CHAR_VERTICAL_TAB = (char) 11;
    public static final char CHAR_ESC = (char) 27;

    private enum EscapingState {
        ReadingNewCharacter,
        ReadingPotentialEscapeSequence,
        ReadingDecimalCharEscape,
        ReadingPotentialHexCharEscape,
        ReadingHexCharEscape
    }

    @NotNull
    @Override
    public String getText() {
        return INTENTION_NAME;
    }

    @NotNull
    @Override
    public String getFamilyName() {
        return FAMILY_NAME;
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) {
        PsiFile containingFile = psiElement.getContainingFile();
        //noinspection SimplifiableIfStatement
        if (containingFile instanceof PhpExpressionCodeFragment) return false;
        return isPhpStringLiteralDoubleQuote(psiElement);
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException {
        if (!isPhpStringLiteralDoubleQuote(psiElement)) return;
        PsiElement parentPsi = psiElement.getParent();
        if (!(parentPsi instanceof StringLiteralExpression)) return;
        String stringLiteralContent = getPhpDoubleQuotedStringRealContent(psiElement);
        StringLiteralExpression phpSingleQuotedStringLiteralPsi = getPhpSingleQuotedStringLiteralPsiFromText(psiElement.getProject(), stringLiteralContent);
        parentPsi.replace(phpSingleQuotedStringLiteralPsi);
    }

    private boolean isPhpStringLiteralDoubleQuote(PsiElement psiElement) {
        if (psiElement instanceof PhpFile)
            return false;
        ASTNode astNode = psiElement.getNode();
        if (astNode == null)
            return false;
        if (astNode.getElementType() == PhpTokenTypes.STRING_LITERAL || astNode.getElementType() == PhpTokenTypes.chLDOUBLE_QUOTE)
            return true;
        if (psiElement instanceof StringLiteralExpression && astNode.getFirstChildNode().getElementType() == PhpTokenTypes.chLDOUBLE_QUOTE)
            return true;
        PsiElement parentPsi = psiElement.getParent();
        boolean isDoubleQuotedString = parentPsi != null && isPhpStringLiteralDoubleQuote(parentPsi);
        return isDoubleQuotedString;
    }

    private String getPhpDoubleQuotedStringRealContent(PsiElement psiElement) {
        String phpStringLiteralText = psiElement.getText();
        String unescapedContent = phpStringLiteralText.substring(1, phpStringLiteralText.length() - 1);
        return unescapePhpDoubleQuotedNonComplexStringContent(unescapedContent);
    }

    private String unescapePhpDoubleQuotedNonComplexStringContent(String text) {
        StringBuilder escapedContentBuffer = new StringBuilder();
        EscapingState escapingState = EscapingState.ReadingNewCharacter;
        StringBuilder currentEscapeBuffer = new StringBuilder();
        for (char currentCharacter : text.toCharArray()) {
            boolean currentCharIsDigit = Character.isDigit(currentCharacter);
            if ((escapingState == EscapingState.ReadingPotentialEscapeSequence && currentCharIsDigit) ||
                escapingState == EscapingState.ReadingDecimalCharEscape) {
                escapingState = EscapingState.ReadingDecimalCharEscape;
                if (currentCharIsDigit) {
                    currentEscapeBuffer.append(currentCharacter);
                }
                if (!currentCharIsDigit || currentEscapeBuffer.length() == 3) {
                    // sequence broken or max length reached, process current buffer and empty it
                    escapedContentBuffer.append((char) Integer.parseInt(currentEscapeBuffer.toString()));
                    currentEscapeBuffer.setLength(0);
                    // stop decimal sequence reading
                    escapingState = EscapingState.ReadingNewCharacter;
                    if (currentCharIsDigit) {
                        // current character has just been processed, continue with next character
                        continue;<commit_msg>Improve complex double quoted strings detection Fix bug when just first child PsiElement's content was taken form complex string Fix parser error on applying intention to simple double quoted string<commit_after>package com.axeldev;

import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.PhpExpressionCodeFragment;
import com.jetbrains.php.lang.psi.PhpFile;
import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import org.jetbrains.annotations.NotNull;

public class PhpReplaceQuotesWithConcatenationIntention extends PsiElementBaseIntentionAction {

    public static final String FAMILY_NAME = "Replace quotes";
    // TODO mention also variable concatenation, make title dynamic?
    public static final String INTENTION_NAME = "Replace quotes with unescaping and variable";
    public static final char CHAR_VERTICAL_TAB = (char) 11;
    public static final char CHAR_ESC = (char) 27;

    private enum EscapingState {
        ReadingNewCharacter,
        ReadingPotentialEscapeSequence,
        ReadingDecimalCharEscape,
        ReadingPotentialHexCharEscape,
        ReadingHexCharEscape
    }

    @NotNull
    @Override
    public String getText() {
        return INTENTION_NAME;
    }

    @NotNull
    @Override
    public String getFamilyName() {
        return FAMILY_NAME;
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) {
        PsiFile containingFile = psiElement.getContainingFile();
        //noinspection SimplifiableIfStatement
        if (containingFile instanceof PhpExpressionCodeFragment) return false;
        return getPhpDoubleQuotedStringLiteralExpression(psiElement) != null;
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException {
        PsiElement stringLiteralExpressionPsi = getPhpDoubleQuotedStringLiteralExpression(psiElement);
        String stringLiteralContent = getPhpDoubleQuotedStringRealContent(stringLiteralExpressionPsi);
        StringLiteralExpression phpSingleQuotedStringLiteralPsi = getPhpSingleQuotedStringLiteralPsiFromText(psiElement.getProject(), stringLiteralContent);
        stringLiteralExpressionPsi.replace(phpSingleQuotedStringLiteralPsi);
    }

    private PsiElement getPhpDoubleQuotedStringLiteralExpression(PsiElement psiElement) {
        if (psiElement instanceof PhpFile) return null;
        if (psiElement instanceof StringLiteralExpression) {
            PsiElement firstChild = psiElement.getFirstChild();
            if (firstChild != null) {
                ASTNode astNode = firstChild.getNode();
                if (astNode.getElementType() == PhpTokenTypes.STRING_LITERAL || astNode.getElementType() == PhpTokenTypes.chLDOUBLE_QUOTE) {
                    return psiElement;
                }
            }
        }
        PsiElement parentPsi = psiElement.getParent();
        return parentPsi != null ? getPhpDoubleQuotedStringLiteralExpression(parentPsi) : null;
    }

    private String getPhpDoubleQuotedStringRealContent(PsiElement psiElement) {
        String phpStringLiteralText = psiElement.getText();
        String unescapedContent = phpStringLiteralText.substring(1, phpStringLiteralText.length() - 1);
        return unescapePhpDoubleQuotedNonComplexStringContent(unescapedContent);
    }

    private String unescapePhpDoubleQuotedNonComplexStringContent(String text) {
        StringBuilder escapedContentBuffer = new StringBuilder();
        EscapingState escapingState = EscapingState.ReadingNewCharacter;
        StringBuilder currentEscapeBuffer = new StringBuilder();
        for (char currentCharacter : text.toCharArray()) {
            boolean currentCharIsDigit = Character.isDigit(currentCharacter);
            if ((escapingState == EscapingState.ReadingPotentialEscapeSequence && currentCharIsDigit) ||
                escapingState == EscapingState.ReadingDecimalCharEscape) {
                escapingState = EscapingState.ReadingDecimalCharEscape;
                if (currentCharIsDigit) {
                    currentEscapeBuffer.append(currentCharacter);
                }
                if (!currentCharIsDigit || currentEscapeBuffer.length() == 3) {
                    // sequence broken or max length reached, process current buffer and empty it
                    escapedContentBuffer.append((char) Integer.parseInt(currentEscapeBuffer.toString()));
                    currentEscapeBuffer.setLength(0);
                    // stop decimal sequence reading
                    escapingState = EscapingState.ReadingNewCharacter;
                    if (currentCharIsDigit) {
                        // current character has just been processed, continue with next character
                        continue;
{'fraction_non_alphanumeric': 0.06729370697178319, 'fraction_numerical': 0.0015514399301852031, 'mean_word_length': 3.4361290322580644, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2495930', 'n_tokens_mistral': 2667, 'n_tokens_neox': 2533, 'n_words': 574}