Skip to content

[Bug]: Rename variable refactor does not rename all occurances #6591

@kahgoh

Description

@kahgoh

Describe the bug

After updating Spoon from 11.2.0 to 11.3.0, I've noticed Spoon does not rename occurrences of a variable arounnd if statements. As far as I know, this was actually working in 11.2.0.

Please note, I've encountered two different cases (although I'm not sure if they have the same root cause), so I've provided them as two different classes in source code sample.

Source code you are trying to analyze/transform

class RaindropConverter {
        String convert(int number) {
            StringBuffer buffer = new StringBuffer();
    
            if(number % 3 == 0)
                buffer.append("Pling");
            if(number % 5 == 0)
                buffer.append("Plang");
            if(number % 7 == 0)
                buffer.append("Plong");
            if(buffer.length() == 0)
                buffer.append(number);
    
            return buffer.toString();
        }
    }

    class AffineCipher {
        private static final int ALPHABET_SIZE = 26;
        private static final int ALPHABET_START_UNICODE = 97;
    
        private static int modInverse(int keyA) {
            return keyA + 1;
        }
        
        private static String translate(String text, int keyA, int keyB, Mode mode) {
            int inv = modInverse(keyA);
            if (inv == 1) {
                throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
            }

            StringBuilder stringBuilder = new StringBuilder();

            text.chars()
                    .filter(Character::isLetterOrDigit)
                    .map(Character::toLowerCase)
                    .forEach(c -> {
                        int orig = c - ALPHABET_START_UNICODE;
                        if (orig < 0) {
                            stringBuilder.appendCodePoint(c);
                        } else {
                            if (mode == Mode.ENCODE) {
                                stringBuilder.appendCodePoint(
                                        ALPHABET_START_UNICODE + Math.floorMod(keyA * orig + keyB, ALPHABET_SIZE)
                                );
                            } else if (mode == Mode.DECODE) {
                                stringBuilder.appendCodePoint(
                                        ALPHABET_START_UNICODE + Math.floorMod(inv * (orig - keyB), ALPHABET_SIZE)
                                );
                            }
                        }
                    });

            return stringBuilder.toString();
        }
    }

Source code for your Spoon processing

import spoon.Launcher;
import spoon.refactoring.CtRenameGenericVariableRefactoring;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtVariable;
import spoon.support.compiler.VirtualFile;

import java.util.Collection;

public class Main {

    private static final String SAMPLE1 = """
    class RaindropConverter {
        String convert(int number) {
            StringBuffer buffer = new StringBuffer();
    
            if(number % 3 == 0)
                buffer.append("Pling");
            if(number % 5 == 0)
                buffer.append("Plang");
            if(number % 7 == 0)
                buffer.append("Plong");
            if(buffer.length() == 0)
                buffer.append(number);
    
            return buffer.toString();
        }
    }

    class AffineCipher {
        private static final int ALPHABET_SIZE = 26;
        private static final int ALPHABET_START_UNICODE = 97;
    
        private static int modInverse(int keyA) {
            return keyA + 1;
        }
        
        private static String translate(String text, int keyA, int keyB, Mode mode) {
            int inv = modInverse(keyA);
            if (inv == 1) {
                throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
            }

            StringBuilder stringBuilder = new StringBuilder();

            text.chars()
                    .filter(Character::isLetterOrDigit)
                    .map(Character::toLowerCase)
                    .forEach(c -> {
                        int orig = c - ALPHABET_START_UNICODE;
                        if (orig < 0) {
                            stringBuilder.appendCodePoint(c);
                        } else {
                            if (mode == Mode.ENCODE) {
                                stringBuilder.appendCodePoint(
                                        ALPHABET_START_UNICODE + Math.floorMod(keyA * orig + keyB, ALPHABET_SIZE)
                                );
                            } else if (mode == Mode.DECODE) {
                                stringBuilder.appendCodePoint(
                                        ALPHABET_START_UNICODE + Math.floorMod(inv * (orig - keyB), ALPHABET_SIZE)
                                );
                            }
                        }
                    });

            return stringBuilder.toString();
        }
    }
    """;

    public static void main(String[] args) {
        Launcher launcher = new Launcher();

        launcher.addInputResource(new VirtualFile(SAMPLE1));
        launcher.getEnvironment().setComplianceLevel(21);

        launcher.getEnvironment().setNoClasspath(true);
        launcher.getEnvironment().setAutoImports(true);

        Collection<CtType<?>> allTypes = launcher.buildModel().getAllTypes();
        for (CtType<?> ctType : allTypes) {
            if (ctType instanceof CtClass<?> ctClass) {
                ctType.getElements(CtVariable.class::isInstance)
                        .forEach(ctElem -> {
                            if (ctElem instanceof CtVariable<?> variable) {
                                new CtRenameGenericVariableRefactoring().setTarget(variable).setNewName(variable.getSimpleName().toUpperCase() + "_1").refactor();
                            }
                        });
            }
        }

        launcher.process();

        System.out.println(getRepresentationString(launcher.getModel()));
    }

    private static String getRepresentationString(CtModel model) {
        var normalized = new StringBuilder();
        for (CtType<?> type : model.getAllTypes()) {
            normalized.append(type.toString());
            normalized.append("\n");
        }
        return normalized.toString();
    }
}

Actual output

class RaindropConverter {
    String convert(int NUMBER_1) {
        StringBuffer BUFFER_1 = new StringBuffer();
        if ((NUMBER_1 % 3) == 0) {
            buffer.append("Pling");
        }
        if ((NUMBER_1 % 5) == 0) {
            buffer.append("Plang");
        }
        if ((NUMBER_1 % 7) == 0) {
            buffer.append("Plong");
        }
        if (buffer.length() == 0) {
            buffer.append(NUMBER_1);
        }
        return BUFFER_1.toString();
    }
}
class AffineCipher {
    private static final int ALPHABET_SIZE_1 = 26;

    private static final int ALPHABET_START_UNICODE_1 = 97;

    private static int modInverse(int KEYA_1) {
        return KEYA_1 + 1;
    }

    private static String translate(String TEXT_1, int KEYA_1, int KEYB_1, Mode MODE_1) {
        int INV_1 = modInverse(KEYA_1);
        if (inv == 1) {
            throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
        }
        StringBuilder STRINGBUILDER_1 = new StringBuilder();
        TEXT_1.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase).forEach(C_1 -> {
            int ORIG_1 = C_1 - ALPHABET_START_UNICODE_1;
            if (orig < 0) {
                STRINGBUILDER_1.appendCodePoint(C_1);
            } else if (MODE_1 == Mode.ENCODE) {
                STRINGBUILDER_1.appendCodePoint(ALPHABET_START_UNICODE_1 + Math.floorMod((KEYA_1 * orig) + KEYB_1, ALPHABET_SIZE_1));
            } else if (MODE_1 == Mode.DECODE) {
                STRINGBUILDER_1.appendCodePoint(ALPHABET_START_UNICODE_1 + Math.floorMod(INV_1 * (orig - KEYB_1), ALPHABET_SIZE_1));
            }
        });
        return STRINGBUILDER_1.toString();
    }
}

Expected output

class RaindropConverter {
    String convert(int NUMBER_1) {
        StringBuffer BUFFER_1 = new StringBuffer();
        if ((NUMBER_1 % 3) == 0) {
            BUFFER_1.append("Pling");
        }
        if ((NUMBER_1 % 5) == 0) {
            BUFFER_1.append("Plang");
        }
        if ((NUMBER_1 % 7) == 0) {
            BUFFER_1.append("Plong");
        }
        if (BUFFER_1.length() == 0) {
            BUFFER_1.append(NUMBER_1);
        }
        return BUFFER_1.toString();
    }
}
class AffineCipher {
    private static final int ALPHABET_SIZE_1 = 26;

    private static final int ALPHABET_START_UNICODE_1 = 97;

    private static int modInverse(int KEYA_1) {
        return KEYA_1 + 1;
    }

    private static String translate(String TEXT_1, int KEYA_1, int KEYB_1, Mode MODE_1) {
        int INV_1 = modInverse(KEYA_1);
        if (INV_1 == 1) {
            throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
        }
        StringBuilder STRINGBUILDER_1 = new StringBuilder();
        TEXT_1.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase).forEach(C_1 -> {
            int ORIG_1 = C_1 - ALPHABET_START_UNICODE_1;
            if (ORIG_1 < 0) {
                STRINGBUILDER_1.appendCodePoint(C_1);
            } else if (MODE_1 == Mode.ENCODE) {
                STRINGBUILDER_1.appendCodePoint(ALPHABET_START_UNICODE_1 + Math.floorMod((KEYA_1 * ORIG_1) + KEYB_1, ALPHABET_SIZE_1));
            } else if (MODE_1 == Mode.DECODE) {
                STRINGBUILDER_1.appendCodePoint(ALPHABET_START_UNICODE_1 + Math.floorMod(INV_1 * (ORIG_1 - KEYB_1), ALPHABET_SIZE_1));
            }
        });
        return STRINGBUILDER_1.toString();
    }
}

Spoon Version

11.3.0

JVM Version

21

What operating system are you using?

Arch Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions