mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 11:50:54 +07:00
SSR: replace multiple occurrences of the same variable correctly (IDEA-65982)
GitOrigin-RevId: 5f0e7dacb53b1f0b1fd1bf4ed846a504acd1a9b9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
07466798de
commit
13d824e053
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {}" +
|
||||
"}";
|
||||
|
||||
Reference in New Issue
Block a user