diff --git a/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXUnusedImportInspectionTest.java b/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXUnusedImportInspectionTest.java new file mode 100644 index 000000000000..a726a9065e1d --- /dev/null +++ b/plugins/javaFX/javaFX-CE/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXUnusedImportInspectionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2013 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.plugins.javaFX.fxml; + +import com.intellij.openapi.application.PluginPathManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxUnusedImportsInspection; + +/** + * User: anna + * Date: 1/10/13 + */ +public class JavaFXUnusedImportInspectionTest extends AbstractJavaFXTestCase { + + @Override + protected void enableInspections() { + myFixture.enableInspections(new JavaFxUnusedImportsInspection()); + } + + + public void testUnusedUnrelatedImports() throws Exception { + myFixture.configureByFile(getTestName(true) + ".fxml"); + myFixture.checkHighlighting(); + } + + @NotNull + @Override + protected String getTestDataPath() { + return PluginPathManager.getPluginHomePath("javaFX") + "/testData/inspections/unusedImport/"; + } +} diff --git a/plugins/javaFX/resources/inspectionDescriptions/JavaFxUnusedImports.html b/plugins/javaFX/resources/inspectionDescriptions/JavaFxUnusedImports.html new file mode 100644 index 000000000000..db9a4019bc0c --- /dev/null +++ b/plugins/javaFX/resources/inspectionDescriptions/JavaFxUnusedImports.html @@ -0,0 +1,6 @@ + + +This inspection reports unused imports in .fxml files. + + + \ No newline at end of file diff --git a/plugins/javaFX/src/META-INF/common-javaFX-plugin.xml b/plugins/javaFX/src/META-INF/common-javaFX-plugin.xml index 187bd9d0d538..444cb9e74abd 100644 --- a/plugins/javaFX/src/META-INF/common-javaFX-plugin.xml +++ b/plugins/javaFX/src/META-INF/common-javaFX-plugin.xml @@ -19,6 +19,9 @@ enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxDefaultTagInspection"/> + + diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/JavaFxImportsOptimizer.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/JavaFxImportsOptimizer.java index a88c7762e811..f3e73b687eff 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/JavaFxImportsOptimizer.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/JavaFxImportsOptimizer.java @@ -118,43 +118,52 @@ public class JavaFxImportsOptimizer implements ImportOptimizer { } private static void collectNamesToImport(@NotNull final Collection> names, XmlFile file) { - file.accept(new XmlRecursiveElementVisitor(){ + file.accept(new JavaFxUsedClassesVisitor() { @Override - public void visitXmlProlog(XmlProlog prolog) {} - - @Override - public void visitXmlProcessingInstruction(XmlProcessingInstruction processingInstruction) {} - - @Override - public void visitXmlAttribute(XmlAttribute attribute) { - final XmlAttributeDescriptor descriptor = attribute.getDescriptor(); - if (descriptor instanceof JavaFxStaticPropertyAttributeDescriptor) { - final PsiElement declaration = descriptor.getDeclaration(); - if (declaration instanceof PsiMember) { - appendClassName(((PsiMember)declaration).getContainingClass()); - } - } - } - - @Override - public void visitXmlTag(XmlTag tag) { - super.visitXmlTag(tag); - final XmlElementDescriptor descriptor = tag.getDescriptor(); - if (descriptor instanceof JavaFxClassBackedElementDescriptor) { - appendClassName(descriptor.getDeclaration()); - } else if (descriptor instanceof JavaFxPropertyElementDescriptor && ((JavaFxPropertyElementDescriptor)descriptor).isStatic()) { - final PsiElement declaration = descriptor.getDeclaration(); - if (declaration instanceof PsiMember) { - appendClassName(((PsiMember)declaration).getContainingClass()); - } - } - } - - private void appendClassName(PsiElement declaration) { - if (declaration instanceof PsiClass) { - names.add(Pair.create(((PsiClass)declaration).getQualifiedName(), false)); - } + protected void appendClassName(String fqn) { + names.add(Pair.create(fqn, false)); } }); - } + } + + public static abstract class JavaFxUsedClassesVisitor extends XmlRecursiveElementVisitor { + @Override + public void visitXmlProlog(XmlProlog prolog) {} + + @Override + public void visitXmlProcessingInstruction(XmlProcessingInstruction processingInstruction) {} + + @Override + public void visitXmlAttribute(XmlAttribute attribute) { + final XmlAttributeDescriptor descriptor = attribute.getDescriptor(); + if (descriptor instanceof JavaFxStaticPropertyAttributeDescriptor) { + final PsiElement declaration = descriptor.getDeclaration(); + if (declaration instanceof PsiMember) { + appendClassName((PsiElement)((PsiMember)declaration).getContainingClass()); + } + } + } + + @Override + public void visitXmlTag(XmlTag tag) { + super.visitXmlTag(tag); + final XmlElementDescriptor descriptor = tag.getDescriptor(); + if (descriptor instanceof JavaFxClassBackedElementDescriptor) { + appendClassName(descriptor.getDeclaration()); + } else if (descriptor instanceof JavaFxPropertyElementDescriptor && ((JavaFxPropertyElementDescriptor)descriptor).isStatic()) { + final PsiElement declaration = descriptor.getDeclaration(); + if (declaration instanceof PsiMember) { + appendClassName((PsiElement)((PsiMember)declaration).getContainingClass()); + } + } + } + + private void appendClassName(PsiElement declaration) { + if (declaration instanceof PsiClass) { + appendClassName(((PsiClass)declaration).getQualifiedName()); + } + } + + protected abstract void appendClassName(String fqn); + } } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxUnusedImportsInspection.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxUnusedImportsInspection.java new file mode 100644 index 000000000000..73e727c9ab3a --- /dev/null +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/codeInsight/inspections/JavaFxUnusedImportsInspection.java @@ -0,0 +1,123 @@ +/* + * Copyright 2000-2013 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.plugins.javaFX.fxml.codeInsight.inspections; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInspection.*; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlProcessingInstruction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.javaFX.fxml.JavaFxFileTypeFactory; +import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil; +import org.jetbrains.plugins.javaFX.fxml.codeInsight.JavaFxImportsOptimizer; + +import java.util.*; + +/** + * User: anna + * Date: 4/18/13 + */ +public class JavaFxUnusedImportsInspection extends XmlSuppressableInspectionTool { + @Nullable + @Override + public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, final boolean isOnTheFly) { + if (!JavaFxFileTypeFactory.isFxml(file)) return null; + final XmlDocument document = ((XmlFile)file).getDocument(); + if (document == null) return null; + final Set usedNames = new HashSet(); + file.accept(new JavaFxImportsOptimizer.JavaFxUsedClassesVisitor() { + @Override + protected void appendClassName(String fqn) { + usedNames.add(fqn); + final String packageName = StringUtil.getPackageName(fqn); + if (!StringUtil.isEmpty(packageName)) { + usedNames.add(packageName); + } + } + }); + + final InspectionManager inspectionManager = InspectionManager.getInstance(file.getProject()); + + final List problems = new ArrayList(); + final Collection instructions = + PsiTreeUtil.findChildrenOfType(document.getProlog(), XmlProcessingInstruction.class); + final Map targetProcessingInstructions = new LinkedHashMap(); + for (XmlProcessingInstruction instruction : instructions) { + final String target = JavaFxPsiUtil.getInstructionTarget("import", instruction); + if (target != null) { + targetProcessingInstructions.put(target, instruction); + } + } + for (String target : targetProcessingInstructions.keySet()) { + final XmlProcessingInstruction instruction = targetProcessingInstructions.get(target); + if (target.endsWith(".*")) { + if (!usedNames.contains(StringUtil.trimEnd(target, ".*"))) { + problems.add(inspectionManager + .createProblemDescriptor(instruction, "Unused import", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, isOnTheFly, + new JavaFxOptimizeImportsFix())); + } + } + else if (!usedNames.contains(target) || targetProcessingInstructions.containsKey(StringUtil.getPackageName(target) + ".*")) { + problems.add(inspectionManager + .createProblemDescriptor(instruction, "Unused import", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, + isOnTheFly, + new JavaFxOptimizeImportsFix())); + } + } + return problems.isEmpty() ? null : problems.toArray(new ProblemDescriptor[problems.size()]); + } + + private static class JavaFxOptimizeImportsFix implements LocalQuickFix { + @NotNull + @Override + public String getName() { + return QuickFixBundle.message("optimize.imports.fix"); + } + + @Override + @NotNull + public String getFamilyName() { + return QuickFixBundle.message("optimize.imports.fix"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement psiElement = descriptor.getPsiElement(); + if (psiElement == null) return; + final PsiFile file = psiElement.getContainingFile(); + if (file == null || !JavaFxFileTypeFactory.isFxml(file)) return; + if (!CodeInsightUtilBase.prepareFileForWrite(file)) return; + ImportOptimizer optimizer = new JavaFxImportsOptimizer(); + final Runnable runnable = optimizer.processFile(file); + new WriteCommandAction.Simple(project, getFamilyName(), file) { + @Override + protected void run() throws Throwable { + runnable.run(); + } + }.execute(); + } + } +} diff --git a/plugins/javaFX/testData/inspections/unusedImport/unusedUnrelatedImports.fxml b/plugins/javaFX/testData/inspections/unusedImport/unusedUnrelatedImports.fxml new file mode 100644 index 000000000000..5aa3b3b63666 --- /dev/null +++ b/plugins/javaFX/testData/inspections/unusedImport/unusedUnrelatedImports.fxml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file