SSR: replace multiple occurrences of the same variable correctly (IDEA-65982)

GitOrigin-RevId: 5f0e7dacb53b1f0b1fd1bf4ed846a504acd1a9b9
This commit is contained in:
Bas Leijdekkers
2019-09-03 15:29:06 +02:00
committed by intellij-monorepo-bot
parent 07466798de
commit 13d824e053
4 changed files with 65 additions and 73 deletions

View File

@@ -700,7 +700,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
@Override
public void visitNameValuePair(PsiNameValuePair pair) {
super.visitNameValuePair(pair);
setParameterContext(pair, pair.getName(), pair.getValue());
setParameterContext(pair, pair.getNameIdentifier(), pair.getValue());
}
@Override
@@ -709,15 +709,12 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
if (scope instanceof PsiCatchSection || scope instanceof PsiForeachStatement) {
return;
}
setParameterContext(parameter, parameter.getName(), parameter.getTypeElement());
setParameterContext(parameter, parameter.getNameIdentifier(), parameter.getTypeElement());
}
private void setParameterContext(@Nullable PsiElement element, String name, PsiElement scopeElement) {
if (name == null || !StructuralSearchUtil.isTypedVariable(name)) {
return;
}
final ParameterInfo nameInfo = builder.findParameterization(Replacer.stripTypedVariableDecoration(name));
assert nameInfo != null;
private void setParameterContext(PsiElement element, PsiElement nameIdentifier, @Nullable PsiElement scopeElement) {
final ParameterInfo nameInfo = builder.findParameterization(nameIdentifier);
if (nameInfo == null) return;
nameInfo.setArgumentContext(false);
final THashMap<String, ParameterInfo> infos = new THashMap<>();
infos.put(nameInfo.getName(), nameInfo);
@@ -728,17 +725,17 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
scopeElement.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
String type = element.getText();
final String type = element.getText();
if (StructuralSearchUtil.isTypedVariable(type)) {
type = Replacer.stripTypedVariableDecoration(type);
final ParameterInfo typeInfo = builder.findParameterization(type);
final ParameterInfo typeInfo = builder.findParameterization(element);
if (typeInfo != null) {
typeInfo.setArgumentContext(false);
typeInfo.putUserData(PARAMETER_CONTEXT, Collections.emptyMap());
infos.put(typeInfo.getName(), typeInfo);
return;
}
}
super.visitElement(element);
}
});
}
@@ -777,31 +774,32 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
}
@Override
public int handleSubstitution(ParameterInfo info, MatchResult match, StringBuilder result, int offset, ReplacementInfo replacementInfo) {
public void handleSubstitution(ParameterInfo info, MatchResult match, StringBuilder result, ReplacementInfo replacementInfo) {
if (info.getName().equals(match.getName())) {
final String replacementString;
boolean forceAddingNewLine = false;
int offset = 0;
final PsiElement element = info.getElement();
final Map<String, ParameterInfo> typeInfos = info.getUserData(PARAMETER_CONTEXT);
if (typeInfos != null) {
if (element instanceof PsiParameter) {
final int parameterEnd = offset + info.getStartIndex();
final int parameterEnd = info.getStartIndex();
final int parameterStart = findMethodParameterStart(result, parameterEnd);
final String template = result.substring(parameterStart, parameterEnd);
replacementString = handleParameter(info, replacementInfo, offset - parameterStart, template);
replacementString = handleParameter(info, replacementInfo, -parameterStart, template);
result.delete(parameterStart, parameterEnd);
offset -= template.length();
}
else if (element instanceof PsiNameValuePair) {
final int parameterStart = offset + info.getStartIndex();
final int parameterStart = info.getStartIndex();
final int parameterEnd = findAnnotationParameterEnd(result, parameterStart);
final String template = result.substring(parameterStart, parameterEnd);
replacementString = handleParameter(info, replacementInfo, offset - parameterStart, template);
replacementString = handleParameter(info, replacementInfo, -parameterStart, template);
result.delete(parameterStart, parameterEnd);
}
else {
return offset;
return;
}
}
else if (match.hasChildren() && !match.isScopeMatch()) {
@@ -879,39 +877,36 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
offset = removeExtraSemicolon(info, offset, result, match);
if (forceAddingNewLine && info.isStatementContext()) {
result.insert(info.getStartIndex() + offset + 1, '\n');
offset++;
}
}
return offset;
}
@Override
public int handleNoSubstitution(ParameterInfo info, int offset, StringBuilder result) {
public void handleNoSubstitution(ParameterInfo info, StringBuilder result) {
final PsiElement element = info.getElement();
final PsiElement prevSibling = PsiTreeUtil.skipWhitespacesBackward(element);
if (prevSibling instanceof PsiJavaToken && isRemovableToken(prevSibling)) {
final int start = info.getBeforeDelimiterPos() + offset - (prevSibling.getTextLength() - 1);
final int end = info.getStartIndex() + offset;
final int start = info.getBeforeDelimiterPos() - (prevSibling.getTextLength() - 1);
final int end = info.getStartIndex();
result.delete(start, end);
return offset - (end - start);
return;
}
final PsiElement nextSibling = PsiTreeUtil.skipWhitespacesForward(element);
if (isRemovableToken(nextSibling)) {
final int start = info.getStartIndex() + offset;
final int end = info.getAfterDelimiterPos() + nextSibling.getTextLength() + offset;
final int start = info.getStartIndex();
final int end = info.getAfterDelimiterPos() + nextSibling.getTextLength();
result.delete(start, end);
return offset - 1;
return;
}
else if (element instanceof PsiTypeElement && nextSibling instanceof PsiIdentifier) {
final int start = info.getStartIndex() + offset;
final int end = info.getAfterDelimiterPos() + offset;
final int start = info.getStartIndex();
final int end = info.getAfterDelimiterPos();
result.delete(start, end);
return offset - 1;
return;
}
if (element == null || !(element.getParent() instanceof PsiForStatement)) {
return removeExtraSemicolon(info, offset, result, null);
removeExtraSemicolon(info, 0, result, null);
}
return offset;
}
private static boolean isRemovableToken(PsiElement element) {

View File

@@ -234,11 +234,7 @@ public abstract class StructuralSearchProfile {
public void provideAdditionalReplaceOptions(@NotNull PsiElement node, ReplaceOptions options, ReplacementBuilder builder) {}
public int handleSubstitution(final ParameterInfo info,
MatchResult match,
StringBuilder result,
int offset,
ReplacementInfo replacementInfo) {
public void handleSubstitution(ParameterInfo info, MatchResult match, StringBuilder result, ReplacementInfo replacementInfo) {
if (info.getName().equals(match.getName())) {
final String replacementString;
boolean removeSemicolon = false;
@@ -271,27 +267,22 @@ public abstract class StructuralSearchProfile {
replacementString = match.getMatchImage();
}
offset = Replacer.insertSubstitution(result, offset, info, replacementString);
int offset = Replacer.insertSubstitution(result, 0, info, replacementString);
if (info.isStatementContext() &&
(removeSemicolon || StringUtil.endsWithChar(replacementString, ';') || StringUtil.endsWithChar(replacementString, '}'))) {
final int start = info.getStartIndex() + offset;
result.delete(start, start + 1);
offset--;
}
}
return offset;
}
public int handleNoSubstitution(ParameterInfo info, int offset, StringBuilder result) {
public void handleNoSubstitution(ParameterInfo info, StringBuilder result) {
if (info.isHasCommaBefore()) {
result.delete(info.getBeforeDelimiterPos() + offset, info.getBeforeDelimiterPos() + 1 + offset);
--offset;
result.delete(info.getBeforeDelimiterPos(), info.getBeforeDelimiterPos() + 1);
}
else if (info.isHasCommaAfter()) {
result.delete(info.getAfterDelimiterPos() + offset, info.getAfterDelimiterPos() + 1 + offset);
--offset;
result.delete(info.getAfterDelimiterPos(), info.getAfterDelimiterPos() + 1);
}
return offset;
}
@Contract("null -> false")

View File

@@ -20,18 +20,16 @@ import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import com.intellij.util.containers.MultiMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author maxim
*/
public final class ReplacementBuilder {
private final String replacement;
private final List<ParameterInfo> parameterizations = new SmartList<>();
private final MultiMap<String, ParameterInfo> parameterizations = MultiMap.createLinked();
private final Map<String, ScriptSupport> replacementVarsMap = new HashMap<>();
private final ReplaceOptions options;
private final Project myProject;
@@ -83,7 +81,7 @@ public final class ReplacementBuilder {
}
info.setAfterDelimiterPos(pos);
prevOffset = offset;
parameterizations.add(info);
parameterizations.putValue(name, info);
}
final LanguageFileType fileType = options.getMatchOptions().getFileType();
@@ -104,14 +102,17 @@ public final class ReplacementBuilder {
patternNode.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
final String text = element.getText();
if (StructuralSearchUtil.isTypedVariable(text)) {
final ParameterInfo parameterInfo = findParameterization(Replacer.stripTypedVariableDecoration(text));
if (parameterInfo != null && parameterInfo.getElement() == null) {
parameterInfo.setElement(element);
final Collection<ParameterInfo> infos = findParameterization(Replacer.stripTypedVariableDecoration(text));
for (ParameterInfo info : infos) {
if (info.getElement() == null) {
info.setElement(element);
return;
}
}
}
super.visitElement(element);
}
});
profile.provideAdditionalReplaceOptions(patternNode, options, this);
@@ -132,17 +133,18 @@ public final class ReplacementBuilder {
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(type);
assert profile != null;
int offset = 0;
for (final ParameterInfo info : parameterizations) {
List<ParameterInfo> sorted = new SmartList<>(parameterizations.values());
Collections.sort(sorted, Comparator.comparingInt(ParameterInfo::getStartIndex).reversed());
for (ParameterInfo info : sorted) {
final MatchResult r = replacementInfo.getNamedMatchResult(info.getName());
if (info.isReplacementVariable()) {
offset = Replacer.insertSubstitution(result, offset, info, generateReplacement(info, match));
Replacer.insertSubstitution(result, 0, info, generateReplacement(info, match));
}
else if (r != null) {
offset = profile.handleSubstitution(info, r, result, offset, replacementInfo);
profile.handleSubstitution(info, r, result, replacementInfo);
}
else {
offset = profile.handleNoSubstitution(info, offset, result);
profile.handleNoSubstitution(info, result);
}
}
@@ -154,23 +156,23 @@ public final class ReplacementBuilder {
if (scriptSupport == null) {
final String constraint = options.getVariableDefinition(info.getName()).getScriptCodeConstraint();
final List<String> variableNames =
ContainerUtil.map(options.getVariableDefinitions(), o -> o.getName());
scriptSupport = new ScriptSupport(myProject, StringUtil.unquoteString(constraint), info.getName(), variableNames,
options.getMatchOptions());
final List<String> variableNames = ContainerUtil.map(options.getVariableDefinitions(), o -> o.getName());
scriptSupport =
new ScriptSupport(myProject, StringUtil.unquoteString(constraint), info.getName(), variableNames, options.getMatchOptions());
replacementVarsMap.put(info.getName(), scriptSupport);
}
return scriptSupport.evaluate(match, null);
}
@Nullable
public ParameterInfo findParameterization(String name) {
for (final ParameterInfo info : parameterizations) {
if (info.getName().equals(name)) {
return info;
}
}
public Collection<ParameterInfo> findParameterization(String name) {
return parameterizations.get(name);
}
return null;
public ParameterInfo findParameterization(PsiElement element) {
if (element == null) return null;
final String text = element.getText();
if (!StructuralSearchUtil.isTypedVariable(text)) return null;
return findParameterization(Replacer.stripTypedVariableDecoration(text)).stream()
.filter(info -> info.getElement() == element).findFirst().orElse(null);
}
}

View File

@@ -460,6 +460,10 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase {
String expected1c = "class A { void /**/ b(int c, int d, int e) {} }";
assertEquals("replace multi match parameter", expected1c, replace(in1, "void b(int '_x*);", "void /**/ b(int $x$);"));
String expected1d = "class A { void b(int c, int d, int e) {} void c(int c, int d, int e) {} }";
assertEquals("replace multiple occurrences of the same variable", expected1d, replace(in1, "void b('_T '_p*);", "void b($T$ $p$); " +
"void c($T$ $p$) {}"));
String in2 = "class X {" +
" void x() {}" +
"}";