SSR: simpler Replacer

This commit is contained in:
Bas Leijdekkers
2018-02-26 20:28:36 +01:00
parent 2b0b340c46
commit a162f71077
5 changed files with 205 additions and 256 deletions
@@ -1,3 +1,4 @@
// 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.refactoring.typeMigration;
import com.intellij.openapi.fileTypes.StdFileTypes;
@@ -89,8 +90,7 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase {
final ReplaceOptions options = new ReplaceOptions();
final MatchOptions matchOptions = options.getMatchOptions();
matchOptions.setFileType(StdFileTypes.JAVA);
final Replacer replacer = new Replacer(project, null);
final String replacement = replacer.testReplace(expression.getText(), stringToReplace, replaceByString, options);
final String replacement = Replacer.testReplace(expression.getText(), stringToReplace, replaceByString, options, project);
return (PsiExpression)JavaCodeStyleManager.getInstance(project).shortenClassReferences(expression.replace(
JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(replacement, expression)));
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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.plugin.replace.impl;
import com.intellij.codeInsight.template.Template;
@@ -47,59 +33,62 @@ import java.util.List;
*/
public class Replacer {
private final Project project;
private ReplacementBuilder replacementBuilder;
private ReplaceOptions options;
private ReplacementContext context;
private StructuralReplaceHandler replaceHandler;
private final ReplaceOptions options;
private final StructuralReplaceHandler replaceHandler;
private final ReplacementBuilder replacementBuilder;
private PsiElement lastAffectedElement = null;
public Replacer(Project project, ReplaceOptions options) {
this.project = project;
this.options = options;
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getMatchOptions().getFileType());
assert profile != null;
replaceHandler = profile.getReplaceHandler(new ReplacementContext(options, project));
assert replaceHandler != null;
replacementBuilder = new ReplacementBuilder(this.project, this.options);
}
public static String stripTypedVariableDecoration(final String type) {
return type.substring(1,type.length()-1);
}
return type.substring(1, type.length() - 1);
}
public static int insertSubstitution(StringBuilder result, int offset, final ParameterInfo info, String image) {
if (image.length() > 0) result.insert(offset+ info.getStartIndex(),image);
offset += image.length();
if (!image.isEmpty()) {
result.insert(offset + info.getStartIndex(), image);
offset += image.length();
}
return offset;
}
public String testReplace(String in, String what, String by, ReplaceOptions options) throws IncorrectOperationException {
return testReplace(in, what, by, options, false);
public static String testReplace(String in, String what, String by, ReplaceOptions options, Project project) {
return testReplace(in, what, by, options, project, false);
}
public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern) {
public static String testReplace(String in, String what, String by, ReplaceOptions options, Project project, boolean sourceIsFile) {
FileType type = options.getMatchOptions().getFileType();
return testReplace(in, what, by, options, filePattern, false, type, null);
return testReplace(in, what, by, options, project, sourceIsFile, false, type, null);
}
public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern, boolean createPhysicalFile,
FileType sourceFileType, Language sourceDialect) {
this.options = options;
final MatchOptions matchOptions = this.options.getMatchOptions();
this.options.setReplacement(by);
replacementBuilder=null;
context = null;
replaceHandler = null;
public static String testReplace(String in, String what, String by, ReplaceOptions replaceOptions, Project project, boolean sourceIsFile,
boolean createPhysicalFile, FileType sourceFileType, Language sourceDialect) {
replaceOptions.setReplacement(by);
final MatchOptions matchOptions = replaceOptions.getMatchOptions();
matchOptions.clearVariableConstraints();
matchOptions.fillSearchCriteria(what);
Matcher.validate(project, matchOptions);
checkSupportedReplacementPattern(project, options);
checkSupportedReplacementPattern(project, replaceOptions);
Matcher matcher = new Matcher(project);
final Replacer replacer = new Replacer(project, replaceOptions);
final Matcher matcher = new Matcher(project);
try {
PsiElement firstElement, lastElement, parent;
final PsiElement firstElement, lastElement, parent;
if (options.getMatchOptions().getScope() == null) {
PsiElement[] elements = MatcherImplUtil.createTreeFromText(
if (replaceOptions.getMatchOptions().getScope() == null) {
final PsiElement[] elements = MatcherImplUtil.createTreeFromText(
in,
filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
sourceIsFile ? PatternTreeContext.File : PatternTreeContext.Block,
sourceFileType, sourceDialect, null,
project,
createPhysicalFile
@@ -111,7 +100,7 @@ public class Replacer {
matchOptions.setScope(new LocalSearchScope(elements));
} else {
parent = ((LocalSearchScope)options.getMatchOptions().getScope()).getScope()[0];
parent = ((LocalSearchScope)matchOptions.getScope()).getScope()[0];
firstElement = parent.getFirstChild();
lastElement = parent.getLastChild();
}
@@ -121,24 +110,24 @@ public class Replacer {
final List<ReplacementInfo> resultPtrList = new SmartList<>();
for (final MatchResult result : sink.getMatches()) {
resultPtrList.add(buildReplacement(result));
resultPtrList.add(replacer.buildReplacement(result));
}
int startOffset = firstElement.getTextRange().getStartOffset();
int endOffset = filePattern ? 0 : parent.getTextLength() - lastElement.getTextRange().getEndOffset();
int endOffset = sourceIsFile ? 0 : parent.getTextLength() - lastElement.getTextRange().getEndOffset();
// get nodes from text may contain
PsiElement prevSibling = firstElement.getPrevSibling();
final PsiElement prevSibling = firstElement.getPrevSibling();
if (prevSibling instanceof PsiWhiteSpace) {
startOffset -= prevSibling.getTextLength() - 1;
}
PsiElement nextSibling = lastElement.getNextSibling();
final PsiElement nextSibling = lastElement.getNextSibling();
if (nextSibling instanceof PsiWhiteSpace) {
endOffset -= nextSibling.getTextLength() - 1;
}
replaceAll(resultPtrList);
replacer.replaceAll(resultPtrList);
String result = parent.getText();
result = result.substring(startOffset);
@@ -150,17 +139,13 @@ public class Replacer {
throw new IncorrectOperationException(e);
}
finally {
options.getMatchOptions().setScope(null);
matchOptions.setScope(null);
}
}
public void replaceAll(final List<ReplacementInfo> infos) {
for (ReplacementInfo info : infos) {
PsiElement element = info.getMatch(0);
initContextAndHandler(element);
if (replaceHandler != null) {
replaceHandler.prepare(info);
}
replaceHandler.prepare(info);
}
((ApplicationImpl)ApplicationManager.getApplication()).runWriteActionWithCancellableProgressInDispatchThread(
@@ -201,11 +186,7 @@ public class Replacer {
}
public void replace(ReplacementInfo info) {
initContextAndHandler(info.getMatch(0));
if (replaceHandler != null) {
replaceHandler.prepare(info);
}
replaceHandler.prepare(info);
reformatAndPostProcess(doReplace(info));
}
@@ -218,11 +199,7 @@ public class Replacer {
final PsiElement elementParent = StructuralSearchUtil.getPresentableElement(element).getParent();
CodeStyleManager.getInstance(project).performActionWithFormatterDisabled(
(Runnable)() -> {
if (replaceHandler != null) {
replaceHandler.replace(info, options);
}
}
(Runnable)() -> replaceHandler.replace(info, options)
);
if (!elementParent.isValid() || !elementParent.isWritable()) {
@@ -245,21 +222,7 @@ public class Replacer {
final int parentOffset = elementParent.getTextRange().getStartOffset();
CodeStyleManager.getInstance(project).reformatRange(containingFile, parentOffset, parentOffset + elementParent.getTextLength(), true);
}
if (replaceHandler != null) {
replaceHandler.postProcess(elementParent, options);
}
}
private void initContextAndHandler(PsiElement psiContext) {
if (context == null) {
context = new ReplacementContext(options, project);
}
if (replaceHandler == null) {
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(psiContext);
if (profile != null) {
replaceHandler = profile.getReplaceHandler(context);
}
}
replaceHandler.postProcess(elementParent, options);
}
public static void handleComments(final PsiElement el, final PsiElement replacement, ReplacementInfo replacementInfo) {
@@ -350,10 +313,6 @@ public class Replacer {
public ReplacementInfo buildReplacement(MatchResult result) {
final ReplacementInfoImpl replacementInfo = new ReplacementInfoImpl(result, project);
if (replacementBuilder == null) {
replacementBuilder = new ReplacementBuilder(project, options);
}
replacementInfo.setReplacement(replacementBuilder.process(result, replacementInfo, options.getMatchOptions().getFileType()));
return replacementInfo;
@@ -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.codeInsight.daemon.quickFix.LightQuickFixTestCase;
@@ -7,13 +7,11 @@ import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
import java.io.File;
import java.io.IOException;
abstract class StructuralReplaceTestCase extends LightQuickFixTestCase {
protected Replacer replacer;
protected ReplaceOptions options;
@Override
@@ -23,7 +21,6 @@ abstract class StructuralReplaceTestCase extends LightQuickFixTestCase {
LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_1_4);
options = new ReplaceOptions();
replacer = new Replacer(getProject(), null);
}
protected String loadFile(String fileName) throws IOException {
@@ -2,6 +2,7 @@
package com.intellij.structuralsearch;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
import com.intellij.testFramework.PlatformTestUtil;
import org.jetbrains.annotations.NotNull;
@@ -13,22 +14,14 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
super.setUp();
options.getMatchOptions().setFileType(StdFileTypes.XML);
}
public void testReplaceXmlAndHtml() {
String s1 = "<a/>";
String s2 = "<a/>";
String s3 = "<a><b/></a>";
String expectedResult = "<a><b/></a>";
String actualResult = replacer.testReplace(s1,s2,s3,options);
assertEquals(
"First tag replacement",
expectedResult,
actualResult
);
assertEquals("First tag replacement", expectedResult, Replacer.testReplace(s1, s2, s3, options, getProject()));
String s4 = "<group id=\"EditorTabPopupMenu\">\n" +
" <reference id=\"Compile\"/>\n" +
@@ -42,7 +35,6 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
String s5 = "<reference id=\"'_Value\"/>";
String s6 = "<reference ref=\"$Value$\"/>";
actualResult = replacer.testReplace(s4,s5,s6,options);
expectedResult = "<group id=\"EditorTabPopupMenu\">\n" +
" <reference ref=\"Compile\"/>\n" +
" <reference ref=\"RunContextPopupGroup\"/>\n" +
@@ -52,42 +44,32 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
" <separator/>\n" +
" <reference ref=\"ExternalToolsGroup\"/>\n" +
"</group>";
assertEquals(
"Replace tag",
expectedResult,
actualResult
);
assertEquals("Replace tag", expectedResult, Replacer.testReplace(s4, s5, s6, options, getProject()));
String s7 = "<h4 class=\"a\">My title<aaa>ZZZZ</aaa> My title 3</h4>\n" +
"<h4>My title 2</h4>";
String s8 = "<h4 class=\"a\">'_Content*</h4>";
String s9 = "<h5>$Content$</h5>";
actualResult = replacer.testReplace(s7,s8,s9,options);
expectedResult = "<h5>My title <aaa>ZZZZ</aaa> My title 3</h5>\n" +
"<h4>My title 2</h4>";
assertEquals(
"Replace tag saving content",
expectedResult,
actualResult
);
assertEquals("Replace tag saving content", expectedResult, Replacer.testReplace(s7, s8, s9, options, getProject()));
expectedResult = "\n" +
"<h4>My title 2</h4>";
assertEquals("Delete tag", expectedResult, replacer.testReplace(s7, s8, "", options));
assertEquals("Delete tag", expectedResult, Replacer.testReplace(s7, s8, "", options, getProject()));
String what = "<'_H:h4 class=\"a\">'_Content*</'_H>";
String by = "<$H$>$Content$</$H$>";
expectedResult = "<h4>My title <aaa>ZZZZ</aaa> My title 3</h4>\n" +
"<h4>My title 2</h4>";
assertEquals("Replace with variable", expectedResult, replacer.testReplace(s7, what, by, options));
assertEquals("Replace with variable", expectedResult, Replacer.testReplace(s7, what, by, options, getProject()));
String in = "<b>Cry 'Havoc!', and <i>let slip the<br> dogs of war</i></b>";
what = "<'_Tag:b >'_Content2*</'_Tag>";
by = "<$Tag$ id=\"unique\">$Content2$</$Tag$>";
expectedResult = "<b id=\"unique\">Cry 'Havoc!', and <i>let slip the<br> dogs of war</i></b>";
assertEquals("Replace complex content with variable", expectedResult, replacer.testReplace(in, what, by, options));
assertEquals("Replace complex content with variable", expectedResult, Replacer.testReplace(in, what, by, options, getProject()));
}
public void testHtmlReplacement1() throws IOException {
@@ -108,7 +90,7 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
String by = "<input $a$ id=\"someId1\" />";
String expected = "<input class=\"other\" type=\"text\" ng-model=\"someModel\" placeholder=\"Some placeholder\" id=\"someId1\" />";
String actual = replacer.testReplace(in, what, by, options);
String actual = Replacer.testReplace(in, what, by, options, getProject());
assertEquals(expected, actual);
}
@@ -130,7 +112,7 @@ public class XmlStructuralReplaceTest extends StructuralReplaceTestCase {
String replacement = loadFile(replacementFileName);
String expectedResult = loadFile(outFileName);
assertEquals(message, expectedResult, replacer.testReplace(content,pattern,replacement,options,filepattern));
assertEquals(message, expectedResult, Replacer.testReplace(content, pattern, replacement, options,getProject(), filepattern));
options.getMatchOptions().setFileType(StdFileTypes.XML);
}