mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
PY-23500 Impl considering dependencies for introduce constant fix
(cherry picked from commit fe2adaeabbf1862c2f51a93df14995264a251cca) IJ-MR-5221 GitOrigin-RevId: 08d0db849d31cdf7684a1b7a68d68072cc0d3686
This commit is contained in:
committed by
intellij-monorepo-bot
parent
96fd6d2148
commit
aa5eb8dc43
@@ -62,6 +62,7 @@ refactoring.introduce.variable.scope.error=The name clashes with an existing var
|
|||||||
# introduce constant
|
# introduce constant
|
||||||
refactoring.introduce.constant.dialog.title=Extract Constant
|
refactoring.introduce.constant.dialog.title=Extract Constant
|
||||||
refactoring.introduce.constant.scope.error=The name is already declared in the scope
|
refactoring.introduce.constant.scope.error=The name is already declared in the scope
|
||||||
|
refactoring.introduce.constant.cannot.extract.selected.expression=Selected expression cannot be extracted into a constant
|
||||||
|
|
||||||
# introduce parameter
|
# introduce parameter
|
||||||
refactoring.extract.parameter.dialog.title=Extract Parameter
|
refactoring.extract.parameter.dialog.title=Extract Parameter
|
||||||
|
|||||||
@@ -430,14 +430,11 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
|||||||
|
|
||||||
private void performActionOnElement(IntroduceOperation operation) {
|
private void performActionOnElement(IntroduceOperation operation) {
|
||||||
if (!checkEnabled(operation)) {
|
if (!checkEnabled(operation)) {
|
||||||
|
showCanNotIntroduceErrorHint(operation.getProject(), operation.getEditor());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PsiElement element = operation.getElement();
|
final PsiElement element = operation.getElement();
|
||||||
|
final PyExpression initializer = getInitializerForElement(element);
|
||||||
final PsiElement parent = element.getParent();
|
|
||||||
final PyExpression initializer = parent instanceof PyAssignmentStatement ?
|
|
||||||
((PyAssignmentStatement)parent).getAssignedValue() :
|
|
||||||
(PyExpression)element;
|
|
||||||
operation.setInitializer(initializer);
|
operation.setInitializer(initializer);
|
||||||
|
|
||||||
if (initializer != null) {
|
if (initializer != null) {
|
||||||
@@ -451,6 +448,8 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
|||||||
performActionOnElementOccurrences(operation);
|
performActionOnElementOccurrences(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void showCanNotIntroduceErrorHint(@NotNull Project project, @NotNull Editor editor) {}
|
||||||
|
|
||||||
protected void performActionOnElementOccurrences(final IntroduceOperation operation) {
|
protected void performActionOnElementOccurrences(final IntroduceOperation operation) {
|
||||||
final Editor editor = operation.getEditor();
|
final Editor editor = operation.getEditor();
|
||||||
if (editor.getSettings().isVariableInplaceRenameEnabled()) {
|
if (editor.getSettings().isVariableInplaceRenameEnabled()) {
|
||||||
@@ -473,6 +472,13 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected @Nullable PyExpression getInitializerForElement(@Nullable PsiElement element) {
|
||||||
|
if (element == null) return null;
|
||||||
|
final PsiElement parent = element.getParent();
|
||||||
|
return parent instanceof PyAssignmentStatement ? ((PyAssignmentStatement)parent).getAssignedValue() :
|
||||||
|
element instanceof PyExpression ? (PyExpression)element : null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void performInplaceIntroduce(IntroduceOperation operation) {
|
protected void performInplaceIntroduce(IntroduceOperation operation) {
|
||||||
final PsiElement statement = performRefactoring(operation);
|
final PsiElement statement = performRefactoring(operation);
|
||||||
if (statement instanceof PyAssignmentStatement) {
|
if (statement instanceof PyAssignmentStatement) {
|
||||||
|
|||||||
@@ -15,22 +15,33 @@
|
|||||||
*/
|
*/
|
||||||
package com.jetbrains.python.refactoring.introduce.constant;
|
package com.jetbrains.python.refactoring.introduce.constant;
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.psi.util.PsiUtilCore;
|
||||||
|
import com.intellij.refactoring.RefactoringBundle;
|
||||||
|
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||||
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import com.jetbrains.python.PyBundle;
|
import com.jetbrains.python.PyBundle;
|
||||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||||
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
|
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
|
||||||
import com.jetbrains.python.psi.PyExpression;
|
import com.jetbrains.python.psi.*;
|
||||||
import com.jetbrains.python.psi.PyFile;
|
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||||
import com.jetbrains.python.psi.PyParameterList;
|
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||||
import com.jetbrains.python.refactoring.PyReplaceExpressionUtil;
|
import com.jetbrains.python.refactoring.PyReplaceExpressionUtil;
|
||||||
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
|
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
|
||||||
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
|
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
|
||||||
|
import one.util.streamex.StreamEx;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Alexey.Ivanov
|
* @author Alexey.Ivanov
|
||||||
@@ -49,16 +60,26 @@ public class PyIntroduceConstantHandler extends IntroduceHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PsiElement addDeclaration(@NotNull final PsiElement expression,
|
protected PsiElement addDeclaration(@NotNull PsiElement expression,
|
||||||
@NotNull final PsiElement declaration,
|
@NotNull PsiElement declaration,
|
||||||
@NotNull final IntroduceOperation operation) {
|
@NotNull IntroduceOperation operation) {
|
||||||
final PsiElement anchor = expression.getContainingFile();
|
PsiElement containingFile = expression.getContainingFile();
|
||||||
assert anchor instanceof PyFile;
|
assert containingFile instanceof PyFile;
|
||||||
return anchor.addBefore(declaration, AddImportHelper.getFileInsertPosition((PyFile)anchor));
|
PsiElement initialPosition = AddImportHelper.getFileInsertPosition((PyFile)containingFile);
|
||||||
|
|
||||||
|
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation);
|
||||||
|
PsiElement maxPosition = getLowermostTopLevelStatement(sameFileRefs);
|
||||||
|
|
||||||
|
if (maxPosition == null) {
|
||||||
|
return containingFile.addBefore(declaration, initialPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert PyUtil.isTopLevel(maxPosition);
|
||||||
|
return containingFile.addAfter(declaration, maxPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Collection<String> generateSuggestedNames(@NotNull final PyExpression expression) {
|
protected Collection<String> generateSuggestedNames(@NotNull PyExpression expression) {
|
||||||
Collection<String> names = new HashSet<>();
|
Collection<String> names = new HashSet<>();
|
||||||
for (String name : super.generateSuggestedNames(expression)) {
|
for (String name : super.generateSuggestedNames(expression)) {
|
||||||
names.add(StringUtil.toUpperCase(name));
|
names.add(StringUtil.toUpperCase(name));
|
||||||
@@ -71,6 +92,65 @@ public class PyIntroduceConstantHandler extends IntroduceHandler {
|
|||||||
return super.isValidIntroduceContext(element) || PsiTreeUtil.getParentOfType(element, PyParameterList.class) != null;
|
return super.isValidIntroduceContext(element) || PsiTreeUtil.getParentOfType(element, PyParameterList.class) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkEnabled(@NotNull IntroduceOperation operation) {
|
||||||
|
PsiElement selectionElement = getOriginalSelectionCoveringElement(operation.getElement());
|
||||||
|
|
||||||
|
PsiFile containingFile = selectionElement.getContainingFile();
|
||||||
|
if (!(containingFile instanceof PyFile)) return false;
|
||||||
|
|
||||||
|
Editor editor = operation.getEditor();
|
||||||
|
if (editor == null) return false;
|
||||||
|
|
||||||
|
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation);
|
||||||
|
if (!ContainerUtil.all(sameFileRefs, it -> PyUtil.isTopLevel(it))) return false;
|
||||||
|
PsiElement maxPosition = getLowermostTopLevelStatement(sameFileRefs);
|
||||||
|
if (maxPosition == null) return true;
|
||||||
|
return PsiUtilCore.compareElementsByPosition(maxPosition, selectionElement) <= 0 &&
|
||||||
|
!PsiTreeUtil.isAncestor(maxPosition, selectionElement, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull List<PsiElement> collectReferencedDefinitionsInSameFile(@NotNull IntroduceOperation operation) {
|
||||||
|
PsiElement selectionElement = getOriginalSelectionCoveringElement(operation.getElement());
|
||||||
|
TextRange textRange = getTextRangeForOperationElement(operation.getElement());
|
||||||
|
|
||||||
|
return StreamEx.of(PsiTreeUtil.collectElementsOfType(selectionElement, PyReferenceExpression.class))
|
||||||
|
.filter(it -> textRange.contains(it.getTextRange()))
|
||||||
|
.filter(ref -> !ref.isQualified())
|
||||||
|
.flatMap(expr -> PyResolveUtil.resolveLocally(expr).stream())
|
||||||
|
.filter(it -> it != null && it.getContainingFile() == operation.getFile())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull TextRange getTextRangeForOperationElement(@NotNull PsiElement operationElement) {
|
||||||
|
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||||
|
if (userData == null || userData.first == null || userData.second == null) {
|
||||||
|
return operationElement.getTextRange();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return userData.second.shiftRight(userData.first.getTextOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull PsiElement getOriginalSelectionCoveringElement(@NotNull PsiElement operationElement) {
|
||||||
|
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||||
|
return userData == null ? operationElement : userData.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable PsiElement getLowermostTopLevelStatement(@NotNull List<PsiElement> elements) {
|
||||||
|
return StreamEx.of(elements)
|
||||||
|
.map(it -> PyPsiUtils.getParentRightBefore(it, it.getContainingFile()))
|
||||||
|
.select(PyStatement.class)
|
||||||
|
.max(PsiUtilCore::compareElementsByPosition)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void showCanNotIntroduceErrorHint(@NotNull Project project, @NotNull Editor editor) {
|
||||||
|
String message =
|
||||||
|
RefactoringBundle.getCannotRefactorMessage(PyBundle.message("refactoring.introduce.constant.cannot.extract.selected.expression"));
|
||||||
|
CommonRefactoringUtil.showErrorHint(project, editor, message, myDialogTitle, getHelpId());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getHelpId() {
|
protected String getHelpId() {
|
||||||
return "python.reference.introduceConstant";
|
return "python.reference.introduceConstant";
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from six import PY2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def ascii(obj):
|
||||||
|
...
|
||||||
|
a = ascii(42) + 'foo'
|
||||||
|
|
||||||
|
|
||||||
|
def func(p):
|
||||||
|
X = a
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from six import PY2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def ascii(obj):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def func(p):
|
||||||
|
X = <selection>ascii(42) + 'foo'</selection>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from six import PY2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def ascii(obj):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def func(p):
|
||||||
|
X = <selection>ascii(p) + 'foo'</selection>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
SUFFIX = "foo"
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
from sys import version
|
||||||
|
print(<selection>version + SUFFIX</selection>)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
SUFFIX = "foo"
|
||||||
|
from sys import version
|
||||||
|
|
||||||
|
a = version + SUFFIX
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
print(a)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
SUFFIX = "foo"
|
||||||
|
from sys import version
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
print(<selection>version + SUFFIX</selection>)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
X = 42
|
||||||
|
N = 42
|
||||||
|
A = 11
|
||||||
|
M = 24
|
||||||
|
K = 21
|
||||||
|
T = 28
|
||||||
|
a = N + M + T + 1
|
||||||
|
O = 22
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
print(a)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
X = 42
|
||||||
|
N = 42
|
||||||
|
A = 11
|
||||||
|
M = 24
|
||||||
|
K = 21
|
||||||
|
T = 28
|
||||||
|
O = 22
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
print(<selection>N + M + T + 1</selection>)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
N = 42
|
||||||
|
|
||||||
|
def f(K):
|
||||||
|
for I in range(0, 10):
|
||||||
|
for j in range(0, 10):
|
||||||
|
print(<selection>N + K + I</selection>)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
N = 42
|
||||||
|
|
||||||
|
def f(K):
|
||||||
|
for i in range(0, 10):
|
||||||
|
print(<selection>N + K</selection>)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
N = 42
|
||||||
|
a = N + 1
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
print(a)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
N = 42
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
print(<selection>N + 1</selection>)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
if True:
|
||||||
|
X = 1
|
||||||
|
else:
|
||||||
|
X = 2
|
||||||
|
a = X + 1
|
||||||
|
|
||||||
|
print(a)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
if True:
|
||||||
|
X = 1
|
||||||
|
else:
|
||||||
|
X = 2
|
||||||
|
|
||||||
|
print(<selection>X + 1</selection>)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
def f(K):
|
||||||
|
for I in range(0, 10):
|
||||||
|
N = 42
|
||||||
|
for j in range(0, 10):
|
||||||
|
print(<selection>N + K + I</selection>)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
def foo():
|
||||||
|
N = 42
|
||||||
|
K = 24
|
||||||
|
|
||||||
|
def f():
|
||||||
|
print(<selection>N + 1</selection>)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
with 42 as N:
|
||||||
|
print(<selection>N + 1</selection>)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def func(param):
|
||||||
|
return par<selection>am + 42</selection>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
SOME_GLOBAL = 42
|
||||||
|
a = 2 + SOME_GLOBAL<caret>
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
return 1 + a
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
SOME_GLOBAL = 42
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
return 1 + <selection>2 + SOME_GLOBAL</selection>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def func(p):
|
||||||
|
return 1 + <selection>2 + p</selection>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2000-2021 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-2021 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.jetbrains.python.refactoring;
|
package com.jetbrains.python.refactoring;
|
||||||
|
|
||||||
|
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||||
import com.intellij.testFramework.TestDataPath;
|
import com.intellij.testFramework.TestDataPath;
|
||||||
import com.jetbrains.python.psi.LanguageLevel;
|
import com.jetbrains.python.psi.LanguageLevel;
|
||||||
import com.jetbrains.python.psi.PyExpression;
|
import com.jetbrains.python.psi.PyExpression;
|
||||||
@@ -43,6 +44,85 @@ public class PyIntroduceConstantTest extends PyIntroduceTestCase {
|
|||||||
doTest();
|
doTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterGlobalVariableOnWhichDepends() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterAllGlobalVariablesOnWhichDepends() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterWithStatementOnWhichDependsRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterLocalVariableOnWhichDependsRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterFunctionParameterOnWhichDependsRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterForIteratorOnWhichDependsRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterLocalVariableInForLoopOnWhichDependsRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testInsertAfterIfElse() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testFromImportTopLevel() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testFromImportInFunctionRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testExpressionWithFunctionCall() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testExpressionWithParameterRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testSubexpressionWithParameterRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testSubexpressionWithGlobal() {
|
||||||
|
doTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PY-23500
|
||||||
|
public void testSubexpressionNotFullWordRefactoringError() {
|
||||||
|
doTestThrowsRefactoringErrorHintException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTestThrowsRefactoringErrorHintException() {
|
||||||
|
assertThrows(CommonRefactoringUtil.RefactoringErrorHintException.class, () -> doTest());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTestDataPath() {
|
protected String getTestDataPath() {
|
||||||
return super.getTestDataPath() + "/refactoring/introduceConstant";
|
return super.getTestDataPath() + "/refactoring/introduceConstant";
|
||||||
|
|||||||
Reference in New Issue
Block a user