Name suggester + tests.

Fixed commented introduce variable tests.
This commit is contained in:
Alefas
2012-02-07 18:22:27 +04:00
parent ddbd86cdf5
commit 1d88960a19
20 changed files with 431 additions and 44 deletions
@@ -42,7 +42,12 @@ public class JetNamedFunction extends JetFunction implements StubBasedPsiElement
@Override
public boolean hasBlockBody() {
return findChildByType(JetTokens.EQ) == null;
return getEqualsToken() == null;
}
@Nullable
public PsiElement getEqualsToken() {
return findChildByType(JetTokens.EQ);
}
@NotNull
+5
View File
@@ -0,0 +1,5 @@
fun x(x: Int = 1) {}
/*
val i = 1
fun x(x: Int = i) {}
*/
@@ -1,29 +1,182 @@
package org.jetbrains.jet.plugin.refactoring;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.psi.JetFile;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.lang.types.JetStandardLibrary;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeUtils;
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
import org.jetbrains.jet.lexer.JetLexer;
import org.jetbrains.jet.lexer.JetTokens;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* User: Alefas
* Date: 31.01.12
*/
public class JetNameSuggester {
public static String[] suggestNames(JetExpression expression) {
String[] def = {"i"};
private JetNameSuggester() {
}
private static void addName(ArrayList<String> result, String name, JetNameValidator validator) {
if (name == "class") name = "clazz";
if (!isIdentifier(name)) return;
String newName = validator.validateName(name);
if (newName == null) return;
result.add(newName);
}
/**
* Name suggestion types:
* 1. According to type:
* 1a. Primitive types to some short name
* 1b. Class types according to class name camel humps: (AbCd => {abCd, cd})
* 1c. Arrays => arrayOfInnerType
* 2. Reference expressions according to reference name camel humps
* 3. Method call expression according to method callee expression
* @param expression to suggest name for variable
* @param validator to check scope for such names
* @return possible names
*/
public static String[] suggestNames(JetExpression expression, JetNameValidator validator) {
ArrayList<String> result = new ArrayList<String>();
BindingContext bindingContext = AnalyzerFacade.analyzeFileWithCache((JetFile) expression.getContainingFile(),
AnalyzerFacade.SINGLE_DECLARATION_PROVIDER);
JetType jetType = bindingContext.get(BindingContext.EXPRESSION_TYPE, expression);
if (jetType == null) return def;
JetType booleanType = JetStandardLibrary.getJetStandardLibrary(expression.getProject()).getBooleanType();
if (JetTypeChecker.INSTANCE.equalTypes(jetType, booleanType)) {
return new String[] {"b"};
if (jetType != null) {
addNamesForType(result, jetType, validator);
}
return new String[] {"i"}; //todo:
addNamesForExpression(result, expression, validator);
if (result.isEmpty()) addName(result, "value", validator);
return ArrayUtil.toStringArray(result);
}
private static void addNamesForType(ArrayList<String> result, JetType jetType, JetNameValidator validator) {
JetStandardLibrary standardLibrary = JetStandardLibrary.getJetStandardLibrary(validator.getProject());
JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
if (typeChecker.equalTypes(standardLibrary.getBooleanType(), jetType)) {
addName(result, "b", validator);
} else if (typeChecker.equalTypes(standardLibrary.getIntType(), jetType)) {
addName(result, "i", validator);
} else if (typeChecker.equalTypes(standardLibrary.getByteType(), jetType)) {
addName(result, "byte", validator);
} else if (typeChecker.equalTypes(standardLibrary.getLongType(), jetType)) {
addName(result, "l", validator);
} else if (typeChecker.equalTypes(standardLibrary.getFloatType(), jetType)) {
addName(result, "fl", validator);
} else if (typeChecker.equalTypes(standardLibrary.getDoubleType(), jetType)) {
addName(result, "d", validator);
} else if (typeChecker.equalTypes(standardLibrary.getShortType(), jetType)) {
addName(result, "sh", validator);
} else if (typeChecker.equalTypes(standardLibrary.getCharType(), jetType)) {
addName(result, "c", validator);
} else if (typeChecker.equalTypes(standardLibrary.getStringType(), jetType)) {
addName(result, "s", validator);
} else {
if (jetType.getArguments().size() == 1) {
JetType argument = jetType.getArguments().get(0).getType();
if (typeChecker.equalTypes(standardLibrary.getArrayType(argument), jetType)) {
if (typeChecker.equalTypes(standardLibrary.getBooleanType(), argument)) {
addName(result, "booleans", validator);
} else if (typeChecker.equalTypes(standardLibrary.getIntType(), argument)) {
addName(result, "ints", validator);
} else if (typeChecker.equalTypes(standardLibrary.getByteType(), argument)) {
addName(result, "bytes", validator);
} else if (typeChecker.equalTypes(standardLibrary.getLongType(), argument)) {
addName(result, "longs", validator);
} else if (typeChecker.equalTypes(standardLibrary.getFloatType(), argument)) {
addName(result, "floats", validator);
} else if (typeChecker.equalTypes(standardLibrary.getDoubleType(), argument)) {
addName(result, "doubles", validator);
} else if (typeChecker.equalTypes(standardLibrary.getShortType(), argument)) {
addName(result, "shorts", validator);
} else if (typeChecker.equalTypes(standardLibrary.getCharType(), argument)) {
addName(result, "chars", validator);
} else if (typeChecker.equalTypes(standardLibrary.getStringType(), argument)) {
addName(result, "strings", validator);
} else {
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(argument);
if (classDescriptor != null) {
String className = classDescriptor.getName();
addName(result, "arrayOf" + StringUtil.capitalize(className) + "s", validator);
}
}
} else {
addForClassType(result, jetType, validator);
}
} else {
addForClassType(result, jetType, validator);
}
}
}
private static void addForClassType(ArrayList<String> result, JetType jetType, JetNameValidator validator) {
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(jetType);
if (classDescriptor != null) {
String className = classDescriptor.getName();
addCamelNames(result, className, validator);
}
}
private static void addCamelNames(ArrayList<String> result, String name, JetNameValidator validator) {
if (name == "") return;
String s = deleteNonLetterFromString(name);
if (s.startsWith("get") || s.startsWith("set")) s = s.substring(0, 3);
else if (s.startsWith("is")) s = s.substring(0, 2);
for (int i = 0; i < s.length(); ++i) {
if (i == 0) {
addName(result, StringUtil.decapitalize(s), validator);
} else if (s.charAt(i) >= 'A' && s.charAt(i) <= 'Z') {
addName(result, StringUtil.decapitalize(s.substring(i)), validator);
}
}
}
private static String deleteNonLetterFromString(String s) {
Pattern pattern = Pattern.compile("[^a-zA-Z]");
Matcher matcher = pattern.matcher(s);
return matcher.replaceAll("");
}
private static void addNamesForExpression(ArrayList<String> result, JetExpression expression, JetNameValidator validator) {
if (expression instanceof JetQualifiedExpression) {
JetQualifiedExpression qualifiedExpression = (JetQualifiedExpression) expression;
addNamesForExpression(result, qualifiedExpression.getSelectorExpression(), validator);
} else if (expression instanceof JetSimpleNameExpression) {
JetSimpleNameExpression reference = (JetSimpleNameExpression) expression;
String referenceName = reference.getReferencedName();
if (referenceName == null) return;
if (referenceName.equals(referenceName.toUpperCase())) {
addName(result, referenceName, validator);
} else {
addCamelNames(result, referenceName, validator);
}
} else if (expression instanceof JetCallExpression) {
JetCallExpression call = (JetCallExpression) expression;
addNamesForExpression(result, call.getCalleeExpression(), validator);
}
}
public static boolean isIdentifier(String name) {
ApplicationManager.getApplication().assertReadAccessAllowed();
if (name == null || name.isEmpty()) return false;
JetLexer lexer = new JetLexer();
lexer.start(name, 0, name.length());
if (lexer.getTokenType() != JetTokens.IDENTIFIER) return false;
lexer.advance();
return lexer.getTokenType() == null;
}
}
@@ -0,0 +1,21 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
/**
* User: Alefas
* Date: 07.02.12
*/
public interface JetNameValidator {
/**
* Validates name, and slightly improves it by adding number to name in case of conflicts
* @param name to check it in scope
* @return name or nameI, where I is number
*/
@Nullable
String validateName(String name);
Project getProject();
}
@@ -0,0 +1,40 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
/**
* User: Alefas
* Date: 07.02.12
*/
public class JetNameValidatorImpl implements JetNameValidator {
public static JetNameValidator getEmptyValidator(final Project project) {
return new JetNameValidator() {
@Override
public String validateName(String name) {
return name;
}
@Override
public Project getProject() {
return project;
}
};
}
private final PsiElement myPlace;
public JetNameValidatorImpl(PsiElement place) {
myPlace = place;
}
@Nullable
public String validateName(String name) {
return name;
}
public Project getProject() {
return myPlace.getProject();
}
}
@@ -1,5 +1,5 @@
cannot.refactor.not.expression=Cannot refactor not expression.
cannot.refactor.not.expression=Cannot refactor not expression
expressions.title=Expressions
introduce.variable=Introduce Variable
cannot.refactor.no.container=Cannot refactor without refactoring container.
cannot.refactor.no.container=Cannot refactor in this place
cannot.refactor.no.expression=Cannot perform refactoring without an expression
@@ -11,6 +11,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
@@ -18,10 +19,8 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.plugin.refactoring.JetIntroduceHandlerBase;
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringUtil;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.refactoring.*;
import java.util.ArrayList;
import java.util.Collections;
@@ -82,7 +81,7 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
allReplaces = Collections.singletonList(expression);
}
String[] suggestedNames = JetNameSuggester.suggestNames(expression); //todo: add name validator
String[] suggestedNames = JetNameSuggester.suggestNames(expression, new JetNameValidatorImpl(expression));
final LinkedHashSet<String> suggestedNamesSet = new LinkedHashSet<String>();
Collections.addAll(suggestedNamesSet, suggestedNames);
PsiElement commonParent = PsiTreeUtil.findCommonParent(allReplaces);
@@ -103,13 +102,14 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
editor.getCaretModel().moveToOffset(property.getTextOffset());
editor.getSelectionModel().removeSelection();
if (isInplaceAvailableOnDataContext) {
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiDocumentManager.getInstance(project).
doPostponedOperationsAndUnblockDocument(editor.getDocument());
JetInplaceVariableIntroducer variableIntroducer =
new JetInplaceVariableIntroducer(property, editor, project, INTRODUCE_VARIABLE,
references.toArray(new JetExpression[references.size()]),
reference.get(), finalReplaceOccurrence,
property);
PsiDocumentManager.getInstance(project).
doPostponedOperationsAndUnblockDocument(editor.getDocument());
variableIntroducer.performInplaceRefactoring(suggestedNamesSet);
}
}
@@ -163,7 +163,6 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
}
boolean needBraces = !(commonContainer instanceof JetBlockExpression ||
commonContainer instanceof JetClassBody ||
commonContainer instanceof JetFile ||
commonContainer instanceof JetClassInitializer);
if (!needBraces) {
property = (JetProperty) commonContainer.addBefore(property, anchor);
@@ -174,7 +173,33 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
emptyBody.addAfter(JetPsiFactory.createWhiteSpace(project, "\n"), firstChild);
property = (JetProperty) emptyBody.addAfter(property, firstChild);
emptyBody.addAfter(JetPsiFactory.createWhiteSpace(project, "\n"), firstChild);
anchor.replace(emptyBody);
emptyBody = (JetExpression) anchor.replace(emptyBody);
for (PsiElement child : emptyBody.getChildren()) {
if (child instanceof JetProperty) {
property = (JetProperty) child;
}
}
if (commonContainer instanceof JetNamedFunction) {
//we should remove equals sign
JetNamedFunction function = (JetNamedFunction) commonContainer;
if (!function.hasDeclaredReturnType()) {
//todo: add return type
}
function.getEqualsToken().delete();
} else if (commonContainer instanceof JetContainerNode) {
JetContainerNode node = (JetContainerNode) commonContainer;
if (node.getParent() instanceof JetIfExpression) {
PsiElement next = node.getNextSibling();
if (next != null) {
PsiElement nextnext = next.getNextSibling();
if (nextnext != null && nextnext.getNode().getElementType() == JetTokens.ELSE_KEYWORD) {
if (next instanceof PsiWhiteSpace) {
next.replace(JetPsiFactory.createWhiteSpace(project, " ")) ;
}
}
}
}
}
}
for (JetExpression replace : allReplaces) {
if (replaceOccurrence) {
@@ -230,7 +255,7 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
@Nullable
private static PsiElement getContainer(PsiElement place) {
if (place instanceof JetBlockExpression || place instanceof JetClassBody || place instanceof JetFile ||
if (place instanceof JetBlockExpression || place instanceof JetClassBody ||
place instanceof JetClassInitializer) {
return place;
}
@@ -242,7 +267,7 @@ public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
return parent;
}
} if (parent instanceof JetBlockExpression || parent instanceof JetWhenEntry ||
parent instanceof JetClassBody || parent instanceof JetFile || parent instanceof JetClassInitializer) {
parent instanceof JetClassBody || parent instanceof JetClassInitializer) {
return parent;
} else if (parent instanceof JetNamedFunction) {
JetNamedFunction function = (JetNamedFunction) parent;
@@ -1,10 +1,10 @@
open class A {
open class A() {
{
do <selection>1</selection> while (true)
}
}
/*
open class A {
open class A() {
{
do {
val i = 1
@@ -1,5 +0,0 @@
fun x(x: Int = <selection>1</selection>) {}
/*
val i = 1
fun x(x: Int = i) {}
*/
@@ -1,13 +1,13 @@
fun a() {
when (1) {
is 1 -> <selection>1</selection>
is 1 -> <selection>2</selection>
}
}
/*
fun a() {
when (1) {
is 1 -> {
val i = 1
val i = 2
}
}
}
@@ -1,10 +1,10 @@
open class A {
open class A() {
{
while (true) <selection>1</selection>
}
}
/*
open class A {
open class A() {
{
while (true) {
val i = 1
@@ -0,0 +1,9 @@
class A() {}
fun a() {
<selection>Array(2) {A()}</selection>
}
/*
array
arrayOfAs
*/
@@ -0,0 +1,7 @@
fun a() {
<selection>Array(2) {"text"}</selection>
}
/*
array
strings
*/
@@ -0,0 +1,10 @@
fun currentTimeMillis(): Long = 1
fun a() {
<selection>currentTimeMillis()</selection>
}
/*
currentTimeMillis
l
millis
timeMillis
*/
@@ -0,0 +1,11 @@
class StringBuilder(s: String) {}
fun a() {
val s = StringBuilder("text")
<selection>s</selection>
}
/*
builder
s
stringBuilder
*/
@@ -0,0 +1,9 @@
fun a() {
val x: Long = 1
<selection>x</selection>
}
/*
l
x
*/
@@ -0,0 +1,10 @@
fun a() {
val aBcD = 1
<selection>aBcD</selection>
}
/*
aBcD
bcD
d
i
*/
@@ -0,0 +1,6 @@
fun a() {
<selection>"test"</selection>
}
/*
s
*/
@@ -21,13 +21,9 @@ public class JetIntroduceVariableTest extends LightCodeInsightFixtureTestCase {
doTest();
}
/*public void testFileLevel() {
doTest();
}
public void testFunctionAddBlock() {
doTest();
}*/
}
public void testIfCondition() {
doTest();
@@ -37,17 +33,17 @@ public class JetIntroduceVariableTest extends LightCodeInsightFixtureTestCase {
doTest();
}
/*public void testIfThenAddBlock() {
public void testIfThenAddBlock() {
doTest();
}*/
}
public void testSimple() {
doTest();
}
/*public void testWhenAddBlock() {
public void testWhenAddBlock() {
doTest();
}*/
}
public void testWhileAddBlock() {
doTest();
@@ -0,0 +1,85 @@
package org.jetbrains.jet.plugin.refactoring.nameSuggester;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.PluginTestCaseBase;
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester;
import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringUtil;
import java.util.Arrays;
/**
* User: Alefas
* Date: 07.02.12
*/
public class JetNameSuggesterTest extends LightCodeInsightFixtureTestCase {
public void testNameArrayOfClasses() {
doTest();
}
public void testNameArrayOfStrings() {
doTest();
}
public void testNameCallExpression() {
doTest();
}
public void testNameClassCamelHump() {
doTest();
}
public void testNameLong() {
doTest();
}
public void testNameReferenceExpression() {
doTest();
}
public void testNameString() {
doTest();
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/refactoring/nameSuggester");
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".kt");
final JetFile file = (JetFile) myFixture.getFile();
PsiElement lastChild = file.getLastChild();
assert lastChild != null;
String expectedResultText = null;
if (lastChild.getNode().getElementType().equals(JetTokens.BLOCK_COMMENT)) {
String lastChildText = lastChild.getText();
expectedResultText = lastChildText.substring(2, lastChildText.length() - 2).trim();
} else if (lastChild.getNode().getElementType().equals(JetTokens.EOL_COMMENT)) {
expectedResultText = lastChild.getText().substring(2).trim();
}
assert expectedResultText != null;
final String finalExpectedResultText = expectedResultText;
try {
JetRefactoringUtil.selectExpression(myFixture.getEditor(), file, new JetRefactoringUtil.SelectExpressionCallback() {
@Override
public void run(@Nullable JetExpression expression) {
String[] names = JetNameSuggester.suggestNames(expression, JetNameValidatorImpl.getEmptyValidator(getProject()));
Arrays.sort(names);
String result = StringUtil.join(names, "\n").trim();
assertEquals(finalExpectedResultText, result);
}
});
} catch (JetRefactoringUtil.IntroduceRefactoringException e) {
throw new AssertionError("Failed to find expression: " + e.getMessage());
}
}
}