From fd3a79474c380abfa2b17dac7a18447b8a9170ee Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Wed, 21 Jan 2015 15:07:43 +0300 Subject: [PATCH] AnnotateCapitalizationIntention --- .../intention/AddAnnotationFix.java | 5 +- .../intention/AddAnnotationPsiFix.java | 15 +-- .../template/LiveTemplateTest.groovy | 4 - .../LightCodeInsightFixtureTestCase.java | 3 + .../LocalQuickFixOnPsiElement.java | 3 +- .../ui/popup/util/BaseListPopupStep.java | 2 +- .../intellij/ui/popup/list/ListPopupImpl.java | 11 +++ .../fixtures/CodeInsightTestUtil.java | 5 +- plugins/devkit/resources/META-INF/plugin.xml | 5 + .../after.java.template | 2 + .../before.java.template | 2 + .../description.html | 5 + .../AnnotateCapitalizationIntention.java | 99 +++++++++++++++++++ .../inspections/capitalization/Intention.java | 4 + .../capitalization/Intention_after.java | 6 ++ .../CapitalizationInspectionTest.java | 11 ++- 16 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/after.java.template create mode 100644 plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/before.java.template create mode 100644 plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/description.html create mode 100644 plugins/devkit/src/inspections/AnnotateCapitalizationIntention.java create mode 100644 plugins/devkit/testData/inspections/capitalization/Intention.java create mode 100644 plugins/devkit/testData/inspections/capitalization/Intention_after.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationFix.java index 1d72ba7728af..91d2440442ab 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationFix.java @@ -23,17 +23,18 @@ import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiNameValuePair; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author ven */ public class AddAnnotationFix extends AddAnnotationPsiFix implements IntentionAction { - public AddAnnotationFix(@NotNull String fqn, @NotNull PsiModifierListOwner modifierListOwner, @NotNull String... annotationsToRemove) { + public AddAnnotationFix(@NotNull String fqn, @Nullable PsiModifierListOwner modifierListOwner, @NotNull String... annotationsToRemove) { this(fqn, modifierListOwner, PsiNameValuePair.EMPTY_ARRAY, annotationsToRemove); } public AddAnnotationFix(@NotNull String fqn, - @NotNull PsiModifierListOwner modifierListOwner, + @Nullable PsiModifierListOwner modifierListOwner, @NotNull PsiNameValuePair[] values, @NotNull String... annotationsToRemove) { super(fqn, modifierListOwner, values, annotationsToRemove); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationPsiFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationPsiFix.java index e5e4bb68d11e..bcd365b1d8f0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationPsiFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/intention/AddAnnotationPsiFix.java @@ -37,10 +37,10 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement { protected final String myAnnotation; protected final String[] myAnnotationsToRemove; protected final PsiNameValuePair[] myPairs; // not used when registering local quick fix - protected final String myText; + protected String myText; public AddAnnotationPsiFix(@NotNull String fqn, - @NotNull PsiModifierListOwner modifierListOwner, + @Nullable PsiModifierListOwner modifierListOwner, @NotNull PsiNameValuePair[] values, @NotNull String... annotationsToRemove) { super(modifierListOwner); @@ -117,26 +117,29 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement { @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - final PsiModifierListOwner myModifierListOwner = (PsiModifierListOwner)startElement; - final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); + invoke(project, file, (PsiModifierListOwner)startElement, myPairs); + } + + protected void invoke(@NotNull Project project, @NotNull PsiFile file, PsiModifierListOwner myModifierListOwner, PsiNameValuePair[] pairs) { final PsiModifierList modifierList = myModifierListOwner.getModifierList(); LOG.assertTrue(modifierList != null); if (modifierList.findAnnotation(myAnnotation) != null) return; + final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); final ExternalAnnotationsManager.AnnotationPlace annotationAnnotationPlace = annotationsManager.chooseAnnotationsPlace(myModifierListOwner); if (annotationAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.NOWHERE) return; if (annotationAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.EXTERNAL) { for (String fqn : myAnnotationsToRemove) { annotationsManager.deannotate(myModifierListOwner, fqn); } - annotationsManager.annotateExternally(myModifierListOwner, myAnnotation, file, myPairs); + annotationsManager.annotateExternally(myModifierListOwner, myAnnotation, file, pairs); } else { final PsiFile containingFile = myModifierListOwner.getContainingFile(); if (!FileModificationService.getInstance().preparePsiElementForWrite(containingFile)) return; removePhysicalAnnotations(myModifierListOwner, myAnnotationsToRemove); - PsiAnnotation inserted = addPhysicalAnnotation(myAnnotation, myPairs, modifierList); + PsiAnnotation inserted = addPhysicalAnnotation(myAnnotation, pairs, modifierList); JavaCodeStyleManager.getInstance(project).shortenClassReferences(inserted); if (containingFile != file) { UndoUtil.markPsiFileForUndo(file); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy index 09a8154c42bd..e683022d017b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy @@ -257,10 +257,6 @@ class Foo { assert 'Bar' in myFixture.lookupElementStrings } - private Editor getEditor() { - return myFixture.getEditor(); - } - private void checkResult() { checkResultByFile(getTestName(false) + "-out.java"); } diff --git a/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java b/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java index e233db10ca9e..f3a39371ac13 100644 --- a/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java +++ b/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java @@ -16,6 +16,7 @@ package com.intellij.testFramework.fixtures; import com.intellij.lang.Language; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; @@ -154,6 +155,8 @@ public abstract class LightCodeInsightFixtureTestCase extends UsefulTestCase{ protected PsiFile getFile() { return myFixture.getFile(); } + protected Editor getEditor() { return myFixture.getEditor(); } + protected PsiManager getPsiManager() { return PsiManager.getInstance(getProject()); } diff --git a/platform/analysis-api/src/com/intellij/codeInspection/LocalQuickFixOnPsiElement.java b/platform/analysis-api/src/com/intellij/codeInspection/LocalQuickFixOnPsiElement.java index 404dc235f3ae..26d0a4efeb62 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/LocalQuickFixOnPsiElement.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/LocalQuickFixOnPsiElement.java @@ -22,13 +22,14 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public abstract class LocalQuickFixOnPsiElement implements LocalQuickFix { protected static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.LocalQuickFixAndIntentionAction"); protected final SmartPsiElementPointer myStartElement; protected final SmartPsiElementPointer myEndElement; - protected LocalQuickFixOnPsiElement(@NotNull PsiElement element) { + protected LocalQuickFixOnPsiElement(@Nullable PsiElement element) { this(element, element); } diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/util/BaseListPopupStep.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/util/BaseListPopupStep.java index 3bb5302e7f2e..2846482091c7 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/util/BaseListPopupStep.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/util/BaseListPopupStep.java @@ -32,7 +32,7 @@ public class BaseListPopupStep extends BaseStep implements ListPopupStep myIcons; private int myDefaultOptionIndex = -1; - public BaseListPopupStep(@Nullable String title, T[] values) { + public BaseListPopupStep(@Nullable String title, T... values) { this(title, values, new Icon[]{}); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java index 83b2322284e9..234214f41500 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java @@ -19,6 +19,8 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.IdeEventQueue; import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.popup.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; @@ -579,4 +581,13 @@ public class ListPopupImpl extends WizardPopup implements ListPopup { return true; } + @Override + public void showInBestPositionFor(@NotNull Editor editor) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + handleSelect(true); + } + else { + super.showInBestPositionFor(editor); + } + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java index e5354bf31edc..875d4e5a928b 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java @@ -75,7 +75,10 @@ public class CodeInsightTestUtil { @TestOnly public static void doIntentionTest(CodeInsightTestFixture fixture, @NonNls String file, @NonNls String actionText) { - doIntentionTest(fixture, actionText, file + ".xml", file + "_after.xml"); + String extension = FileUtilRt.getExtension(file); + file = FileUtil.getNameWithoutExtension(file); + if (extension.isEmpty()) extension = "xml"; + doIntentionTest(fixture, actionText, file + "." + extension, file + "_after." + extension); } @TestOnly diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index cb56660a9d9c..57ccd16f4282 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -136,6 +136,11 @@ groupKey="inspections.group.name" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.idea.devkit.inspections.internal.UnsafeReturnStatementVisitorInspection" /> + + org.jetbrains.idea.devkit.inspections.AnnotateCapitalizationIntention + I18N + + diff --git a/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/after.java.template b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/after.java.template new file mode 100644 index 000000000000..d4f27e16a5b6 --- /dev/null +++ b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/after.java.template @@ -0,0 +1,2 @@ +void setTitle(@Nls(capitalization = Nls.Capitalization.Title) String title) { +} \ No newline at end of file diff --git a/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/before.java.template b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/before.java.template new file mode 100644 index 000000000000..9bfa705cf3da --- /dev/null +++ b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/before.java.template @@ -0,0 +1,2 @@ +void setTitle(String title) { +} \ No newline at end of file diff --git a/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/description.html b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/description.html new file mode 100644 index 000000000000..cede65af080b --- /dev/null +++ b/plugins/devkit/resources/intentionDescriptions/AnnotateCapitalizationIntention/description.html @@ -0,0 +1,5 @@ + + +Annotate with capitalization type. + + \ No newline at end of file diff --git a/plugins/devkit/src/inspections/AnnotateCapitalizationIntention.java b/plugins/devkit/src/inspections/AnnotateCapitalizationIntention.java new file mode 100644 index 000000000000..444c1b2324c9 --- /dev/null +++ b/plugins/devkit/src/inspections/AnnotateCapitalizationIntention.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2015 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.AnnotationUtil; +import com.intellij.codeInsight.intention.AddAnnotationFix; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Dmitry Avdeev + */ +public class AnnotateCapitalizationIntention extends AddAnnotationFix { + + public AnnotateCapitalizationIntention() { + super(Nls.class.getName(), null); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PsiModifierListOwner element = getElement(editor, file); + if (element == null || + (!ApplicationManager.getApplication().isUnitTestMode() && element.getManager().isInProject(element)) || + AnnotationUtil.findAnnotation(element, Nls.class.getName()) != null) return false; + myText = "Annotate capitalization type"; + return true; + } + + @NotNull + @Override + public String getFamilyName() { + return "Annotate capitalization type"; + } + + @Override + public void invoke(@NotNull final Project project, Editor editor, final PsiFile file) throws IncorrectOperationException { + final PsiModifierListOwner modifierListOwner = getElement(editor, file); + if (modifierListOwner == null) throw new IncorrectOperationException(); + + BaseListPopupStep step = + new BaseListPopupStep(null, Nls.Capitalization.Title, Nls.Capitalization.Sentence) { + @Override + public PopupStep onChosen(final Nls.Capitalization selectedValue, boolean finalChoice) { + new WriteCommandAction.Simple(project) { + @Override + protected void run() throws Throwable { + String nls = Nls.class.getName(); + PsiAnnotation annotation = JavaPsiFacade.getInstance(project).getElementFactory() + .createAnnotationFromText("@" + nls + "(capitalization = " + + nls + ".Capitalization." + selectedValue.toString() + ")", modifierListOwner); + invoke(project, file, modifierListOwner, annotation.getParameterList().getAttributes()); + } + }.execute(); + return FINAL_CHOICE; + } + }; + JBPopupFactory.getInstance().createListPopup(step).showInBestPositionFor(editor); + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Nullable + private static PsiModifierListOwner getElement(Editor editor, PsiFile file) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PsiParameter parameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class, false); + if (parameter == null) return null; + PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class); + if (method == null) return null; + PsiType type = parameter.getType(); + return type.equalsToText(CommonClassNames.JAVA_LANG_STRING) ? parameter : null; + } +} diff --git a/plugins/devkit/testData/inspections/capitalization/Intention.java b/plugins/devkit/testData/inspections/capitalization/Intention.java new file mode 100644 index 000000000000..db4ceff293e0 --- /dev/null +++ b/plugins/devkit/testData/inspections/capitalization/Intention.java @@ -0,0 +1,4 @@ +class Intention { + void setTitle(String title) { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/inspections/capitalization/Intention_after.java b/plugins/devkit/testData/inspections/capitalization/Intention_after.java new file mode 100644 index 000000000000..850b727d00c0 --- /dev/null +++ b/plugins/devkit/testData/inspections/capitalization/Intention_after.java @@ -0,0 +1,6 @@ +import org.jetbrains.annotations.Nls; + +class Intention { + void setTitle(@Nls(capitalization = Nls.Capitalization.Title) String title) { + } +} \ No newline at end of file diff --git a/plugins/devkit/testSources/inspections/CapitalizationInspectionTest.java b/plugins/devkit/testSources/inspections/CapitalizationInspectionTest.java index eb3db0dd0a1a..1c4b0b8e718c 100644 --- a/plugins/devkit/testSources/inspections/CapitalizationInspectionTest.java +++ b/plugins/devkit/testSources/inspections/CapitalizationInspectionTest.java @@ -46,7 +46,16 @@ public class CapitalizationInspectionTest extends LightCodeInsightFixtureTestCas assertEmpty(myFixture.filterAvailableIntentions("Properly capitalize")); } - public void doTest(boolean fix) { + public void testIntention() throws Exception { + myFixture.configureByFile("Intention.java"); + AnnotateCapitalizationIntention intention = new AnnotateCapitalizationIntention(); + assertTrue(intention.isAvailable(getProject(), getEditor(), getFile())); + intention.invoke(getProject(), getEditor(), getFile()); + myFixture.checkResultByFile("Intention_after.java"); + assertFalse(intention.isAvailable(getProject(), getEditor(), getFile())); + } + + private void doTest(boolean fix) { myFixture.testHighlighting(getTestName(false) + ".java"); if (!fix) return;