[java-refactoring] IDEA-92605 Detect conflicts for rename local variable

GitOrigin-RevId: 75ad577ef79dac053c689d9eb6d20dec3af222c7
This commit is contained in:
Tagir Valeev
2024-04-10 17:29:29 +02:00
committed by intellij-monorepo-bot
parent 29a66a2ae9
commit 696dfffffc
13 changed files with 239 additions and 27 deletions

View File

@@ -0,0 +1,8 @@
class Test {
void test(Object obj) {
switch (obj) {
case String s:
int <caret>x = 10;
}
}
}

View File

@@ -112,6 +112,12 @@ public class RenameLocalTest extends LightRefactoringTestCase {
() -> doTest("s"));
}
public void testConflictWithPatternInline() {
assertThrows(BaseRefactoringProcessor.ConflictsInTestsException.class,
"An existing pattern variable s has the same name",
() -> doTestInplaceRename("s"));
}
public void testConflictWithFutureVar() {
assertThrows(BaseRefactoringProcessor.ConflictsInTestsException.class,
"An existing local variable <b><code>y</code></b> has the same name",

View File

@@ -6,6 +6,7 @@ import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.ide.DataManager;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
@@ -105,6 +106,7 @@ public class RenameMembersInplaceTest extends LightJavaCodeInsightTestCase {
state = TemplateManagerImpl.getTemplateState(getEditor());
assertNotNull(state);
state.gotoEnd(false);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
checkResultByFile(BASE_PATH + getTestName(false) + "_after.java");
}
@@ -134,6 +136,7 @@ public class RenameMembersInplaceTest extends LightJavaCodeInsightTestCase {
state = TemplateManagerImpl.getTemplateState(editor);
assert state != null;
state.gotoEnd(false);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
checkResultByFile(BASE_PATH + getTestName(false) + "_after.java");
}

View File

@@ -10,8 +10,7 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.ide.IdeEventQueue;
import com.intellij.modcommand.*;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.ui.ChooserInterceptor;
import com.intellij.ui.UiInterceptors;
@@ -21,7 +20,6 @@ import org.jetbrains.annotations.NotNull;
import java.util.regex.Pattern;
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21;
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21_ANNOTATED;
public class ReplaceConstructorWithFactoryTest extends LightRefactoringTestCase {
@@ -53,6 +51,7 @@ public class ReplaceConstructorWithFactoryTest extends LightRefactoringTestCase
LookupElement newMain = ContainerUtil.find(lookup.getItems(), l -> l.getLookupString().equals("newMain"));
assertNotNull(newMain);
((LookupImpl)lookup).finishLookup('\n', newMain);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
checkResultByFile("/refactoring/replaceConstructorWithFactory/afterWithSelection.java");
}

View File

@@ -27094,6 +27094,7 @@ c:com.intellij.refactoring.rename.inplace.MemberInplaceRenamer
- p:collectRefs(com.intellij.psi.search.SearchScope):java.util.Collection
- p:createInplaceRenamerToRestart(com.intellij.psi.PsiNamedElement,com.intellij.openapi.editor.Editor,java.lang.String):com.intellij.refactoring.rename.inplace.VariableInplaceRenamer
- p:createRenameProcessor(com.intellij.psi.PsiElement,java.lang.String):com.intellij.refactoring.rename.RenameProcessor
- p:findCollision():com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo
- p:getNameIdentifier():com.intellij.psi.PsiElement
- p:getRefactoringId():java.lang.String
- p:getReferencesSearchScope(com.intellij.openapi.vfs.VirtualFile):com.intellij.psi.search.SearchScope
@@ -27199,6 +27200,7 @@ c:com.intellij.refactoring.rename.inplace.VariableInplaceRenamer
- p:collectAdditionalElementsToRename(java.util.List):V
- p:createInplaceRenamerToRestart(com.intellij.psi.PsiNamedElement,com.intellij.openapi.editor.Editor,java.lang.String):com.intellij.refactoring.rename.inplace.VariableInplaceRenamer
- p:createLookupExpression(com.intellij.psi.PsiElement):com.intellij.refactoring.rename.inplace.MyLookupExpression
- p:findCollision():com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo
- finish(Z):V
- p:getCommandName():java.lang.String
- p:getRefactoringId():java.lang.String

View File

@@ -16,8 +16,8 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.DumbModeBlockedFunctionality;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
@@ -36,6 +36,7 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.refactoring.rename.RenamePsiElementProcessor;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo;
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageViewUtil;
@@ -101,6 +102,12 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer {
return super.checkLocalScope();
}
@Override
protected @Nullable UnresolvableCollisionUsageInfo findCollision() {
// Collisions for members are processed by RenameProcessor using normal conflicts dialog
return null;
}
@Override
protected PsiElement getNameIdentifier() {
final PsiFile currentFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());

View File

@@ -2,6 +2,7 @@
package com.intellij.refactoring.rename.inplace;
import com.intellij.CommonBundle;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.ide.DataManager;
@@ -11,15 +12,23 @@ import com.intellij.lang.LangBundle;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageExtension;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModCommandExecutor;
import com.intellij.modcommand.ModUpdateFileText;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.command.impl.FinishMarkAction;
import com.intellij.openapi.command.impl.StartMarkAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
@@ -30,7 +39,9 @@ import com.intellij.psi.*;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringElementListener;
@@ -44,14 +55,13 @@ import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Processor;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.*;
public class VariableInplaceRenamer extends InplaceRefactoring {
public static final LanguageExtension<ResolveSnapshotProvider> INSTANCE = new LanguageExtension<>(
@@ -61,6 +71,9 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
private TextRange mySelectedRange;
protected Language myLanguage;
protected @Nullable SuggestedNameInfo mySuggestedNameInfo;
private int myOrigOffset;
private ModUpdateFileText myRevertCommand;
private PsiNamedElement myElementInCopy;
public VariableInplaceRenamer(@NotNull PsiNamedElement elementToRename,
@NotNull Editor editor) {
@@ -135,6 +148,15 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
@NotNull Collection<Pair<PsiElement, TextRange>> stringUsages,
final @NotNull PsiElement scope,
final @NotNull PsiFile containingFile) {
PsiFile fileCopy = (PsiFile)containingFile.copy();
myOrigOffset = myEditor.getCaretModel().getOffset();
try {
myElementInCopy = PsiTreeUtil.findSameElementInCopy(myElementToRename, fileCopy);
}
catch (IllegalStateException | UnsupportedOperationException e) {
// Unsupported synthetic element?
myElementInCopy = null;
}
if (appendAdditionalElement(refs, stringUsages)) {
return super.buildTemplateAndStart(refs, stringUsages, scope, containingFile);
}
@@ -268,10 +290,117 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
JBPopupFactory.getInstance()
.createConfirmation(LangBundle.message("popup.title.inserted.identifier.valid"), IdeBundle.message("label.continue.editing"),
CommonBundle.getCancelButtonText(),
() -> createInplaceRenamerToRestart(variable, myEditor, newName).performInplaceRefactoring(nameSuggestions), 0).showInBestPositionFor(myEditor);
() -> {
startDumbIfPossible();
rollBack();
createInplaceRenamerToRestart(variable, myEditor, newName).performInplaceRefactoring(nameSuggestions);
stopDumbLaterIfPossible();
},
() -> rollBack(), 0).showInBestPositionFor(myEditor);
}
}
/**
* @return unresolvable collision if it's desired to display it to the user; null if refactoring can proceed without additional question
*/
protected @Nullable UnresolvableCollisionUsageInfo findCollision() {
if (myInsertedName.equals(myOldName)) return null;
PsiNamedElement variable = myElementInCopy;
if (variable == null) return null;
String newName = myInsertedName;
List<UsageInfo> result = new ArrayList<>();
RenamePsiElementProcessor.forElement(variable).findCollisions(variable, newName, Map.of(variable, newName), result);
return ContainerUtil.findInstance(result, UnresolvableCollisionUsageInfo.class);
}
private @Nullable UnresolvableRenameProblem findProblem() {
if (!isIdentifier(myInsertedName, myLanguage)) {
return new IllegalIdentifierProblem();
}
UnresolvableCollisionUsageInfo collision = findCollision();
if (collision != null) {
return new Collision(collision);
}
return null;
}
private sealed interface UnresolvableRenameProblem {
void showUI();
}
private final class IllegalIdentifierProblem implements UnresolvableRenameProblem {
@Override
public void showUI() {
performOnInvalidIdentifier(myInsertedName, myNameSuggestions);
}
}
private final class Collision implements UnresolvableRenameProblem {
private final @NotNull UnresolvableCollisionUsageInfo collision;
private Collision(@NotNull UnresolvableCollisionUsageInfo collision) { this.collision = collision; }
@Override
public void showUI() {
RangeHighlighter highlighter = highlightConflictingElement(collision.getElement());
String description = StringUtil.stripHtml(collision.getDescription(), false);
final int offset = myElementInCopy.getTextOffset();
restoreCaretOffset(offset);
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new BaseRefactoringProcessor.ConflictsInTestsException(List.of(description));
}
JBPopupFactory.getInstance().createConfirmation(
description, IdeBundle.message("label.refactor.anyway"),
IdeBundle.message("label.continue.editing"),
() -> {
if (highlighter != null) {
highlighter.dispose();
}
performRefactoringRename(myInsertedName, myMarkAction);
},
() -> {
if (highlighter != null) {
highlighter.dispose();
}
startDumbIfPossible();
rollBack();
PsiNamedElement var = getVariable();
if (var != null) {
createInplaceRenamerToRestart(var, myEditor, myInsertedName).performInplaceRefactoring(myNameSuggestions);
}
stopDumbLaterIfPossible();
},
0)
.showInBestPositionFor(myEditor);
}
}
private void rollBack() {
Document document = InjectedLanguageEditorUtil.getTopLevelEditor(myEditor).getDocument();
PsiFile psiFile = Objects.requireNonNull(PsiDocumentManager.getInstance(myProject).getPsiFile(document));
CommandProcessor.getInstance().executeCommand(myProject, () -> {
ModCommandExecutor.getInstance().executeInBatch(ActionContext.from(null, psiFile), myRevertCommand);
}, getCommandName(), null);
myEditor.getCaretModel().moveToOffset(myOrigOffset);
}
private @Nullable RangeHighlighter highlightConflictingElement(PsiElement conflictingElement) {
if (conflictingElement != null) {
TextRange range = conflictingElement.getTextRange();
if (conflictingElement instanceof PsiNameIdentifierOwner owner) {
PsiElement identifier = owner.getNameIdentifier();
if (identifier != null) {
range = identifier.getTextRange();
}
}
List<RangeHighlighter> highlighters = new ArrayList<>();
HighlightManager.getInstance(myProject).addRangeHighlight(
myEditor, range.getStartOffset(), range.getEndOffset(), EditorColors.SEARCH_RESULT_ATTRIBUTES, false, true, highlighters);
return ContainerUtil.getOnlyItem(highlighters);
}
return null;
}
protected void renameSynthetic(String newName) {
}
@@ -354,6 +483,12 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
}
}
private void startDumbIfPossible() {
if (InjectedLanguageEditorUtil.getTopLevelEditor(myEditor) instanceof EditorImpl editor) {
editor.startDumb();
}
}
protected void stopDumbLaterIfPossible() {
if (InjectedLanguageEditorUtil.getTopLevelEditor(myEditor) instanceof EditorImpl editor) {
editor.stopDumbLater();
@@ -380,17 +515,34 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
}
bind = true;
if (!isIdentifier(myInsertedName, myLanguage)) {
performOnInvalidIdentifier(myInsertedName, myNameSuggestions);
}
else if (mySnapshot != null) {
ApplicationManager.getApplication().runWriteAction(() -> mySnapshot.apply(myInsertedName));
}
performRefactoringRename(myInsertedName, myMarkAction);
ReadAction.nonBlocking(() -> findProblem())
.expireWhen(() -> myEditor.isDisposed() || myProject.isDisposed())
.finishOnUiThread(ModalityState.nonModal(), problem -> {
if (problem == null) {
if (mySnapshot != null) {
ApplicationManager.getApplication().runWriteAction(() -> mySnapshot.apply(myInsertedName));
}
performRefactoringRename(myInsertedName, myMarkAction);
}
else {
problem.showUI();
cancel();
}
})
.submit(AppExecutorUtil.getAppExecutorService());
}
return bind;
}
private void cancel() {
try {
stopDumbLaterIfPossible();
}
finally {
FinishMarkAction.finish(myProject, myEditor, myMarkAction);
}
}
@Override
public void finish(boolean success) {
super.finish(success);
@@ -402,9 +554,26 @@ public class VariableInplaceRenamer extends InplaceRefactoring {
}
}
protected void revertStateOnFinish() {
if (myInsertedName == null || !isIdentifier(myInsertedName, myLanguage)) {
revertState();
private static @NotNull ModUpdateFileText getRevertModCommand(@NotNull Editor editor, String oldName) {
TemplateState state = TemplateManagerImpl.getTemplateState(editor);
int segmentsCount = state == null ? 0 : state.getSegmentsCount();
Document document = editor.getDocument();
String oldText = document.getText();
StringBuilder newText = new StringBuilder();
int lastPos = 0;
List<ModUpdateFileText.Fragment> fragments = new ArrayList<>();
for (int i = 0; i < segmentsCount; i++) {
TextRange segmentRange = state.getSegmentRange(i);
newText.append(oldText, lastPos, segmentRange.getStartOffset());
fragments.add(new ModUpdateFileText.Fragment(newText.length(), segmentRange.getLength(), oldName.length()));
newText.append(oldName);
lastPos = segmentRange.getEndOffset();
}
newText.append(oldText, lastPos, oldText.length());
return new ModUpdateFileText(editor.getVirtualFile(), oldText, newText.toString(), fragments);
}
protected void revertStateOnFinish() {
myRevertCommand = getRevertModCommand(InjectedLanguageEditorUtil.getTopLevelEditor(myEditor), myOldName);
}
}

View File

@@ -1994,6 +1994,7 @@ dialog.title.collect.troubleshooting.information=Collect Troubleshooting Informa
link.reset.icon=Reset
link.change.icon=Change\u2026
dialog.title.can.t.create.0.sdk=Cannot Create {0} SDK
label.refactor.anyway=Refactor anyway
label.continue.editing=Continue editing
button.continue.editing=Continue Editing
button.save.anyway=Save Anyway

View File

@@ -509,4 +509,5 @@ move.handler.is.dumb.during.indexing=Moving files while indexing is in progress
progress.title.collecting.suggested.names=Collecting suggested names
dialog.message.selected.element.used.from.non.project.files=Selected element is used from non-project files. These usages won't be renamed. Proceed anyway?
progress.title.prepare.to.delete=Prepare to delete
progress.title.prepare.to.delete=Prepare to delete
dialog.title.looking.for.collisions=Looking for Collisions

View File

@@ -2,7 +2,6 @@
package com.intellij.testFramework.fixtures;
import com.intellij.codeInsight.daemon.impl.*;
import com.intellij.codeInsight.daemon.impl.AnnotationSessionImpl;
import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor;
import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessors;
import com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler;
@@ -19,7 +18,9 @@ import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.codeInsight.template.impl.actions.ListTemplatesAction;
import com.intellij.ide.DataManager;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.lang.annotation.*;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.lang.surroundWith.Surrounder;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
@@ -255,6 +256,7 @@ public final class CodeInsightTestUtil {
state = TemplateManagerImpl.getTemplateState(editor);
assert state != null;
state.gotoEnd(false);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
}
finally {
Disposer.dispose(disposable);

View File

@@ -5,6 +5,7 @@ import com.intellij.navigation.NavigationItem;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,7 +19,7 @@ import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrVariableEnhancer;
import java.util.Objects;
import java.util.function.Function;
public class ClosureSyntheticParameter extends GrLightParameter implements NavigationItem, GrRenameableLightElement {
public class ClosureSyntheticParameter extends GrLightParameter implements NavigationItem, GrRenameableLightElement, SyntheticElement {
private static final Function<ClosureSyntheticParameter,PsiType> TYPES_CALCULATOR = parameter -> {
PsiType typeGroovy = GrVariableEnhancer.getEnhancedType(parameter);
if (typeGroovy instanceof PsiIntersectionType) {
@@ -40,6 +41,15 @@ public class ClosureSyntheticParameter extends GrLightParameter implements Navig
return myClosure.getElement();
}
@Override
public @NotNull PsiElement findSameElementInCopy(@NotNull PsiFile copy) {
GrClosableBlock block = myClosure.getElement();
if (block == null) {
throw new IllegalStateException("No parent closure");
}
return new ClosureSyntheticParameter(PsiTreeUtil.findSameElementInCopy(block, copy), isOptional());
}
@Override
public PsiElement setName(@NotNull String newName) throws IncorrectOperationException {
if (!newName.equals(getName())) {

View File

@@ -1,7 +1,8 @@
// Copyright 2000-2019 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 org.jetbrains.plugins.groovy.refactoring.rename
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.impl.source.PostprocessReformattingAspect
@@ -555,9 +556,10 @@ class Test {
private def doInplaceRenameTest() {
String prefix = "/${testName.capitalize()}"
fixture.configureByFile prefix + ".groovy"
WriteCommandAction.runWriteCommandAction project, {
CodeInsightTestUtil.doInlineRename(new GrVariableInplaceRenameHandler(), "foo", fixture)
}
CommandProcessor.instance.executeCommand(
project,
{ CodeInsightTestUtil.doInlineRename(new GrVariableInplaceRenameHandler(), "foo", fixture) },
"Rename", null)
fixture.checkResultByFile prefix + "_after.groovy"
}

View File

@@ -4,6 +4,7 @@ package com.jetbrains.python;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ex.InspectionProfileImpl;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.TestDataFile;
@@ -831,6 +832,7 @@ public class PyQuickFixTest extends PyTestCase {
}
if (applyFix) {
myFixture.launchAction(intentionActions.get(0));
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after"), true);
}
}