From d5ebf927b2ea19a86aefb0b4531999c2c04558f6 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Mon, 26 Feb 2018 16:45:02 +0100 Subject: [PATCH] SSR: fix replacing xml tag set as target (IDEA-187291) --- .../StructuralSearchProfile.java | 4 +- .../StructuralSearchUtil.java | 12 +- .../XmlStructuralSearchProfile.java | 116 ++++++++---------- .../XmlStructuralReplaceTest.java | 32 +++++ 4 files changed, 91 insertions(+), 73 deletions(-) diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java index f751d55dbccf..a82d53d8187d 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java @@ -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; } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java index 22eedd9d3fb6..ae420eb36545 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java @@ -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() { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java index 86e136898a3f..38d4b6666dfe 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java @@ -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), diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/XmlStructuralReplaceTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/XmlStructuralReplaceTest.java index 4a052cfed6c0..7fea74aaad08 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/XmlStructuralReplaceTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/XmlStructuralReplaceTest.java @@ -94,6 +94,38 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase { assertEquals(expected, actual); } + public void testRemoveAttribute() { + String in = ""; + String what = ""; + String by = ""; + String expected = ""; + + assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject())); + } + + public void testRemoveTag() { + String in = "\n" + + " liberation\n" + + " remuneration\n" + + ""; + String what = "<'tag:[regex( c )]>'_text"; + String by = ""; + String expected = "\n" + + " liberation\n" + + ""; + + assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject())); + } + + public void testReplaceAttributeValue() { + String in = ""; + String what = "<'_tag '_attr:[regex( id )]=\\''value\\'>"; + String by = "\"two\""; + String expected = ""; + + assertEquals(expected, Replacer.testReplace(in, what, by, options, getProject())); + } + @NotNull @Override protected String getTestDataPath() {