SSR: fix replacing xml tag set as target (IDEA-187291)

This commit is contained in:
Bas Leijdekkers
2018-02-26 20:28:36 +01:00
parent d08ce61cd5
commit d5ebf927b2
4 changed files with 91 additions and 73 deletions
@@ -30,6 +30,7 @@ import com.intellij.structuralsearch.plugin.ui.SearchContext;
import com.intellij.structuralsearch.plugin.ui.UIUtil;
import com.intellij.util.LocalTimeCounter;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -259,6 +260,7 @@ public abstract class StructuralSearchProfile {
return offset;
}
@Contract("null -> false")
public boolean isIdentifier(PsiElement element) {
return false;
}
@@ -272,7 +274,7 @@ public abstract class StructuralSearchProfile {
return false;
}
@NotNull
@Contract("!null -> !null")
public PsiElement getPresentableElement(PsiElement element) {
return isIdentifier(element) ? element.getParent() : element;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.structuralsearch;
import com.intellij.lang.Language;
@@ -46,13 +46,11 @@ public class StructuralSearchUtil {
return !isIdentifier(element) ? element : element.getParent();
}
@NotNull
public static PsiElement getPresentableElement(@NotNull PsiElement element) {
@Contract("!null -> !null")
public static PsiElement getPresentableElement(PsiElement element) {
if (element == null) return null;
final StructuralSearchProfile profile = getProfileByPsiElement(element);
if (profile == null) {
return element;
}
return profile.getPresentableElement(element);
return profile == null ? element : profile.getPresentableElement(element);
}
private static StructuralSearchProfile[] getNewStyleProfiles() {
@@ -153,83 +153,69 @@ public class XmlStructuralSearchProfile extends StructuralSearchProfile {
private static class MyReplaceHandler extends StructuralReplaceHandler {
private final ReplacementContext myContext;
private MyReplaceHandler(ReplacementContext context) {
MyReplaceHandler(ReplacementContext context) {
myContext = context;
}
public void replace(ReplacementInfo info, ReplaceOptions options) {
PsiElement elementToReplace = info.getMatch(0);
final PsiElement elementToReplace = StructuralSearchUtil.getPresentableElement(info.getMatch(0));
assert elementToReplace != null;
PsiElement elementParent = elementToReplace.getParent();
String replacementToMake = info.getReplacement();
boolean listContext = elementToReplace.getParent() instanceof XmlTag;
final String replacementToMake = info.getReplacement();
final PsiElement elementParent = elementToReplace.getParent();
final boolean listContext = elementParent instanceof XmlTag;
if (listContext) {
doReplaceInContext(info, elementToReplace, replacementToMake, elementParent, myContext);
}
PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, myContext);
if (statements.length > 0) {
PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent);
// preserve comments
Replacer.handleComments(elementToReplace, replacement, info);
elementToReplace.replace(replacement);
}
else {
elementToReplace.delete();
}
}
}
final PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, myContext);
if (statements.length > 0) {
PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent);
private static void doReplaceInContext(ReplacementInfo info,
PsiElement elementToReplace,
String replacementToMake,
PsiElement elementParent,
ReplacementContext context) {
PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, context);
if (statements.length > 1) {
elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace);
}
else if (statements.length == 1) {
PsiElement replacement = statements[0];
Replacer.handleComments(elementToReplace, replacement, info);
try {
elementParent.addBefore(replacement, elementToReplace);
}
catch (IncorrectOperationException e) {
elementToReplace.replace(replacement);
}
}
final int matchSize = info.getMatchesCount();
for (int i = 0; i < matchSize; ++i) {
PsiElement element = info.getMatch(i);
if (element == null) continue;
PsiElement firstToDelete = element;
PsiElement lastToDelete = element;
PsiElement prevSibling = element.getPrevSibling();
PsiElement nextSibling = element.getNextSibling();
if (prevSibling instanceof PsiWhiteSpace) {
firstToDelete = prevSibling;
}
else if (prevSibling == null && nextSibling instanceof PsiWhiteSpace) {
lastToDelete = nextSibling;
}
if (nextSibling instanceof XmlText && i + 1 < matchSize) {
final PsiElement next = info.getMatch(i + 1);
if (next != null && next == nextSibling.getNextSibling()) {
lastToDelete = nextSibling;
// preserve comments
Replacer.handleComments(elementToReplace, replacement, info);
elementToReplace.replace(replacement);
}
else {
elementToReplace.delete();
}
}
element.getParent().deleteChildRange(firstToDelete, lastToDelete);
}
private static void doReplaceInContext(ReplacementInfo info,
PsiElement elementToReplace,
String replacementToMake,
PsiElement elementParent,
ReplacementContext context) {
final PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, context);
if (statements.length > 1) {
elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace);
}
else if (statements.length == 1) {
Replacer.handleComments(elementToReplace, statements[0], info);
try {
elementParent.addBefore(statements[0], elementToReplace);
}
catch (IncorrectOperationException e) {
elementToReplace.replace(statements[0]);
}
}
final int matchSize = info.getMatchesCount();
for (int i = 0; i < matchSize; ++i) {
final PsiElement element = StructuralSearchUtil.getPresentableElement(info.getMatch(i));
final PsiElement prevSibling = element.getPrevSibling();
element.getParent().deleteChildRange(isWhitespace(prevSibling) ? prevSibling : element, element);
}
}
private static boolean isWhitespace(PsiElement element) {
if (element instanceof PsiWhiteSpace) return true;
if (!(element instanceof XmlText)) return false;
final PsiElement firstChild = element.getFirstChild();
final PsiElement lastChild = element.getLastChild();
return firstChild == lastChild && firstChild instanceof PsiWhiteSpace;
}
}
@@ -241,7 +227,7 @@ public class XmlStructuralSearchProfile extends StructuralSearchProfile {
private static class XmlPredefinedConfigurations {
private static final String HTML_XML = SSRBundle.message("xml_html.category");
private static Configuration[] createPredefinedTemplates() {
static Configuration[] createPredefinedTemplates() {
return new Configuration[]{
createSearchTemplateInfo("xml tag", "<'a/>", HTML_XML, StdFileTypes.XML),
createSearchTemplateInfo("xml attribute", "<'_tag 'attribute=\"'_value\"/>", HTML_XML, StdFileTypes.XML),
@@ -94,6 +94,38 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
assertEquals(expected, actual);
}
public void testRemoveAttribute() {
String in = "<input class=\"other\" placeholder=\"Some placeholder\">";
String what = "<input 'a:[regex( placeholder )]>";
String by = "";
String expected = "<input class=\"other\">";
assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject()));
}
public void testRemoveTag() {
String in = "<a>\n" +
" <b>liberation</b>\n" +
" <c>remuneration</c>\n" +
"</a>";
String what = "<'tag:[regex( c )]>'_text</'tag>";
String by = "";
String expected = "<a>\n" +
" <b>liberation</b>\n" +
"</a>";
assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject()));
}
public void testReplaceAttributeValue() {
String in = "<input id=\"one\" class=\"no\">";
String what = "<'_tag '_attr:[regex( id )]=\\''value\\'>";
String by = "\"two\"";
String expected = "<input id=\"two\" class=\"no\">";
assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject()));
}
@NotNull
@Override
protected String getTestDataPath() {