Capitalization annotation support

This commit is contained in:
Dmitry Avdeev
2014-12-05 19:06:35 +03:00
parent 423b5bb3f6
commit 6f3577ddf7
10 changed files with 191 additions and 26 deletions

View File

@@ -30,4 +30,15 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE, ElementType.PACKAGE})
public @interface Nls {
enum Capitalization {
NotSpecified,
/** e.g. This Is a Title */
Title,
/** e.g. This is a sentence */
Sentence
}
Capitalization capitalization() default Capitalization.NotSpecified;
}

View File

@@ -423,24 +423,30 @@ public class StringUtil extends StringUtilRt {
@NotNull
@Contract(pure = true)
public static String wordsToBeginFromUpperCase(@NotNull String s) {
return toTitleCase(s, ourPrepositions);
return fixCapitalization(s, ourPrepositions, true);
}
@NotNull
@Contract(pure = true)
public static String wordsToBeginFromLowerCase(@NotNull String s) {
return fixCapitalization(s, ourPrepositions, false);
}
@NotNull
@Contract(pure = true)
public static String toTitleCase(@NotNull String s) {
return toTitleCase(s, ArrayUtil.EMPTY_STRING_ARRAY);
return fixCapitalization(s, ArrayUtil.EMPTY_STRING_ARRAY, true);
}
@NotNull
private static String toTitleCase(@NotNull String s, @NotNull String[] prepositions) {
private static String fixCapitalization(@NotNull String s, @NotNull String[] prepositions, boolean title) {
StringBuilder buffer = null;
for (int i = 0; i < s.length(); i++) {
char prevChar = i == 0 ? ' ' : s.charAt(i - 1);
char currChar = s.charAt(i);
if (!Character.isLetterOrDigit(prevChar) && prevChar != '\'') {
if (Character.isLetterOrDigit(currChar)) {
if (!Character.isUpperCase(currChar)) {
if (title || Character.isUpperCase(currChar)) {
int j = i;
for (; j < s.length(); j++) {
if (!Character.isLetterOrDigit(s.charAt(j))) {
@@ -451,7 +457,7 @@ public class StringUtil extends StringUtilRt {
if (buffer == null) {
buffer = new StringBuilder(s);
}
buffer.setCharAt(i, toUpperCase(currChar));
buffer.setCharAt(i, title ? toUpperCase(currChar) : toLowerCase(currChar));
}
}
}

View File

@@ -130,6 +130,10 @@ public class StringUtilTest extends TestCase {
assertEquals("Couldn't Connect to Debugger", StringUtil.wordsToBeginFromUpperCase("Couldn't connect to debugger"));
}
public void testSentenceCapitalization() {
assertEquals("couldn't connect to debugger", StringUtil.wordsToBeginFromLowerCase("Couldn't Connect to Debugger"));
}
public void testEscapeStringCharacters() {
assertEquals("\\\"\\n", StringUtil.escapeStringCharacters(3, "\\\"\n", "\"", false, new StringBuilder()).toString());
assertEquals("\\\"\\n", StringUtil.escapeStringCharacters(2, "\"\n", "\"", false, new StringBuilder()).toString());

View File

@@ -15,6 +15,7 @@
*/
package org.jetbrains.idea.devkit.inspections;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.*;
import com.intellij.lang.properties.psi.Property;
import com.intellij.lang.properties.references.PropertyReference;
@@ -28,12 +29,15 @@ import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
/**
* @author yole
*/
@@ -67,6 +71,19 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitMethod(PsiMethod method) {
PsiType type = method.getReturnType();
if (!(type instanceof PsiClassType) || !"String".equals(((PsiClassType)type).getClassName())) return;
PsiReturnStatement returnStatement = PsiTreeUtil.findChildOfType(method, PsiReturnStatement.class);
if (returnStatement == null) return;
PsiExpression expression = returnStatement.getReturnValue();
String value = getTitleValue(expression);
if (value == null) return;
Nls.Capitalization capitalization = getCapitalizationFromAnno(method);
checkCapitalization(expression, holder, capitalization);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
PsiReferenceExpression methodExpression = expression.getMethodExpression();
@@ -78,11 +95,7 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
if (args.length == 0) {
return;
}
String titleValue = getTitleValue(args [0]);
if (!hasTitleCapitalization(titleValue)) {
holder.registerProblem(args [0], "Dialog title '" + titleValue + "' is not properly capitalized. It should have title capitalization",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new TitleCapitalizationFix(titleValue));
}
checkCapitalization(args[0], holder, Nls.Capitalization.Title);
}
else if (calledName.startsWith("show") && (calledName.endsWith("Dialog") || calledName.endsWith("Message"))) {
if (!isMethodOfClass(expression, Messages.class.getName())) return;
@@ -93,11 +106,7 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
PsiParameter parameter = parameters[i];
if ("title".equals(parameter.getName()) && i < args.length) {
String titleValue = getTitleValue(args [i]);
if (!hasTitleCapitalization(titleValue)) {
holder.registerProblem(args [i], "Message title '" + titleValue + "' is not properly capitalized. It should have title capitalization",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new TitleCapitalizationFix(titleValue));
}
checkCapitalization(args[i], holder, Nls.Capitalization.Title);
break;
}
}
@@ -106,6 +115,23 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
};
}
public Nls.Capitalization getCapitalizationFromAnno(PsiMethod method) {
PsiAnnotation nls = AnnotationUtil.findAnnotationInHierarchy(method, Collections.singleton(Nls.class.getName()));
if (nls == null) return Nls.Capitalization.NotSpecified;
PsiAnnotationMemberValue capitalization = nls.findAttributeValue("capitalization");
Object cap = JavaPsiFacade.getInstance(method.getProject()).getConstantEvaluationHelper().computeConstantExpression(capitalization);
return cap instanceof Nls.Capitalization ? (Nls.Capitalization)cap : Nls.Capitalization.NotSpecified;
}
private static void checkCapitalization(PsiExpression element, @NotNull ProblemsHolder holder, Nls.Capitalization capitalization) {
String titleValue = getTitleValue(element);
if (!checkCapitalization(titleValue, capitalization)) {
holder.registerProblem(element, "String '" + titleValue + "' is not properly capitalized. It should have " +
StringUtil.toLowerCase(capitalization.toString()) + " capitalization",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new TitleCapitalizationFix(titleValue, capitalization));
}
}
private static boolean isMethodOfClass(PsiMethodCallExpression expression, String... classNames) {
PsiMethod psiMethod = expression.resolveMethod();
if (psiMethod == null) {
@@ -120,7 +146,7 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
}
@Nullable
private static String getTitleValue(PsiExpression arg) {
private static String getTitleValue(@Nullable PsiExpression arg) {
if (arg instanceof PsiLiteralExpression) {
Object value = ((PsiLiteralExpression)arg).getValue();
if (value instanceof String) {
@@ -167,20 +193,35 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
return null;
}
private static boolean hasTitleCapitalization(String value) {
if (value == null) {
private static boolean checkCapitalization(String value, Nls.Capitalization capitalization) {
if (value == null || capitalization == Nls.Capitalization.NotSpecified) {
return true;
}
value = value.replace("&", "");
return StringUtil.wordsToBeginFromUpperCase(value).equals(value);
return capitalization == Nls.Capitalization.Title
? StringUtil.wordsToBeginFromUpperCase(value).equals(value)
: checkSentenceCapitalization(value);
}
private static boolean checkSentenceCapitalization(@NotNull String value) {
String[] words = value.split(" ");
if (words.length == 0) return true;
if (!StringUtil.isCapitalized(words[0])) return false;
for (int i = 1; i < words.length; i++) {
String word = words[i];
if (StringUtil.isCapitalized(word)) return false;
}
return true;
}
private static class TitleCapitalizationFix implements LocalQuickFix {
private final String myTitleValue;
private final Nls.Capitalization myCapitalization;
public TitleCapitalizationFix(String titleValue) {
public TitleCapitalizationFix(String titleValue, Nls.Capitalization capitalization) {
myTitleValue = titleValue;
myCapitalization = capitalization;
}
@NotNull
@@ -217,10 +258,10 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
}
final String string = (String)value;
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
final PsiExpression
newExpression = factory.createExpressionFromText('"' + StringUtil.wordsToBeginFromUpperCase(string) + '"', element);
final PsiExpression newExpression = factory.createExpressionFromText('"' + fixValue(string) + '"', element);
literalExpression.replace(newExpression);
}else if (element instanceof PsiMethodCallExpression) {
}
else if (element instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
final PsiMethod method = methodCallExpression.resolveMethod();
final PsiExpression returnValue = PropertyUtil.getGetterReturnExpression(method);
@@ -235,9 +276,9 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
if (value == null) {
return;
}
final String capitalizedString = StringUtil.wordsToBeginFromUpperCase(value);
property.setValue(capitalizedString);
} else if (element instanceof PsiReferenceExpression) {
property.setValue(fixValue(value));
}
else if (element instanceof PsiReferenceExpression) {
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)element;
final PsiElement target = referenceExpression.resolve();
if (!(target instanceof PsiVariable)) {
@@ -250,6 +291,13 @@ public class TitleCapitalizationInspection extends BaseJavaLocalInspectionTool {
}
}
@NotNull
private String fixValue(String string) {
return myCapitalization == Nls.Capitalization.Title
? StringUtil.wordsToBeginFromUpperCase(string)
: StringUtil.capitalize(StringUtil.wordsToBeginFromLowerCase(string));
}
protected static boolean isQuickFixOnReadOnlyFile(PsiElement problemElement) {
final PsiFile containingPsiFile = problemElement.getContainingFile();
if (containingPsiFile == null) {

View File

@@ -0,0 +1,9 @@
import org.jetbrains.annotations.Nls;
class SentenceCapitalization {
@Nls(capitalization = Nls.Capitalization.Sentence)
public String getName1() {
return <warning descr="String 'Foo Bar' is not properly capitalized. It should have sentence capitalization">"Foo B<caret>ar"</warning>;
}
}

View File

@@ -0,0 +1,9 @@
import org.jetbrains.annotations.Nls;
class SentenceCapitalization {
@Nls(capitalization = Nls.Capitalization.Sentence)
public String getName1() {
return "Foo bar";
}
}

View File

@@ -0,0 +1,8 @@
import org.jetbrains.annotations.Nls;
class TitleCapitalization {
@Nls(capitalization = Nls.Capitalization.Title)
public String getName() {
return <warning descr="String 'Foo bar' is not properly capitalized. It should have title capitalization">"Foo b<caret>ar"</warning>;
}
}

View File

@@ -0,0 +1,8 @@
import org.jetbrains.annotations.Nls;
class TitleCapitalization {
@Nls(capitalization = Nls.Capitalization.Title)
public String getName() {
return "Foo Bar";
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2000-2014 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.
*/
package org.jetbrains.idea.devkit.inspections;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
/**
* @author Dmitry Avdeev
*/
public class CapitalizationInspectionTest extends LightCodeInsightFixtureTestCase {
public void testTitleCapitalization() throws Exception {
doTest();
}
public void testSentenceCapitalization() throws Exception {
doTest();
}
public void doTest() {
myFixture.testHighlighting(getTestName(false) + ".java");
final IntentionAction action = myFixture.filterAvailableIntentions("Properly capitalize").get(0);
new WriteCommandAction.Simple(getProject()) {
@Override
protected void run() throws Throwable {
action.invoke(getProject(), myFixture.getEditor(), getFile());
}
}.execute();
myFixture.checkResultByFile(getTestName(false) + "_after.java");
}
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.addClass("package com.intellij.codeInspection; public class CommonProblemDescriptor {}");
myFixture.addClass("package com.intellij.codeInspection; public class QuickFix {}");
myFixture.enableInspections(TitleCapitalizationInspection.class);
}
@Override
protected String getBasePath() {
return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/inspections/capitalization";
}
}