From bf4759fccb47481196c79981c4eb69a7d4221a2d Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 29 Jun 2016 20:58:57 +0300 Subject: [PATCH] [java] module declaration highlighting (file-level) --- .../daemon/impl/analysis/HighlightUtil.java | 5 +- .../impl/analysis/HighlightVisitorImpl.java | 18 +++- .../impl/analysis/ModuleHighlightUtil.java | 99 +++++++++++++++++++ .../daemon/impl/quickfix/MoveFileFix.java | 74 ++++++++++++++ .../src/messages/JavaErrorMessages.properties | 8 +- .../advHighlighting8/ModulesNotSupported.java | 3 + .../daemon/LightAdvHighlightingJdk8Test.java | 5 +- .../daemon/ModuleHighlightingTest.java | 58 +++++++++++ .../LightCodeInsightFixtureTestCase.java | 13 ++- .../src/messages/QuickFixBundle.properties | 5 +- 10 files changed, 275 insertions(+), 13 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting8/ModulesNotSupported.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 9610b22aaac3..b8e3d0218e0a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -2981,7 +2981,8 @@ public class HighlightUtil extends HighlightUtilBase { LAMBDA_EXPRESSIONS(LanguageLevel.JDK_1_8, "feature.lambda.expressions"), TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations"), RECEIVERS(LanguageLevel.JDK_1_8, "feature.type.receivers"), - REFS_AS_RESOURCE(LanguageLevel.JDK_1_9, "feature.try.with.resources.refs"); + REFS_AS_RESOURCE(LanguageLevel.JDK_1_9, "feature.try.with.resources.refs"), + MODULES(LanguageLevel.JDK_1_9, "feature.modules"); private final LanguageLevel level; private final String key; @@ -3007,4 +3008,4 @@ public class HighlightUtil extends HighlightUtilBase { return null; } -} +} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index d65d195af7c8..388c819d202a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -1598,19 +1598,27 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkReceiverType(parameter)); } + @Override + public void visitModule(PsiJavaModule module) { + super.visitModule(module); + if (!myHolder.hasErrorResults()) myHolder.add(checkFeature(module, Feature.MODULES)); + if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileName(module, myFile)); + if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileDuplicates(module, myFile)); + if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileLocation(module, myFile)); + } + @Nullable private HighlightInfo checkFeature(@NotNull PsiElement element, @NotNull Feature feature) { return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile); } protected void prepareToRunAsInspection(@NotNull HighlightInfoHolder holder) { - myHolder = holder; + PsiFile file = holder.getContextFile(); + JavaSdkVersion sdkVersion = JavaVersionService.getInstance().getJavaSdkVersion(file); - final PsiFile file = holder.getContextFile(); + myHolder = holder; myFile = file; myLanguageLevel = PsiUtil.getLanguageLevel(file); - myJavaSdkVersion = ObjectUtils.notNull(JavaVersionService.getInstance().getJavaSdkVersion(file), - JavaSdkVersion.fromLanguageLevel(myLanguageLevel)); - + myJavaSdkVersion = sdkVersion != null ? sdkVersion : JavaSdkVersion.fromLanguageLevel(myLanguageLevel); } } \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java new file mode 100644 index 000000000000..078e2fdff13d --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2016 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 com.intellij.codeInsight.daemon.impl.analysis; + +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.quickfix.MoveFileFix; +import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.impl.scopes.ModulesScope; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaModule; +import com.intellij.psi.PsiJavaModuleReference; +import com.intellij.psi.search.FilenameIndex; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; + +public class ModuleHighlightUtil { + private static final String MODULE_FILE_NAME = "module-info.java"; + + @Nullable + static HighlightInfo checkFileName(@NotNull PsiJavaModule element, @NotNull PsiFile file) { + if (!MODULE_FILE_NAME.equals(file.getName())) { + String message = JavaErrorMessages.message("module.file.wrong.name"); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(element)).description(message).create(); + QuickFixAction.registerQuickFixAction(info, factory().createRenameFileFix(MODULE_FILE_NAME)); + return info; + } + + return null; + } + + @Nullable + static HighlightInfo checkFileDuplicates(@NotNull PsiJavaModule element, @NotNull PsiFile file) { + VirtualFile vFile = file.getVirtualFile(); + if (vFile != null) { + Project project = file.getProject(); + Module module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(vFile); + if (module != null) { + Collection others = + FilenameIndex.getVirtualFilesByName(project, MODULE_FILE_NAME, new ModulesScope(Collections.singleton(module), project)); + if (others.size() > 1) { + String message = JavaErrorMessages.message("module.file.duplicate"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(element)).description(message).create(); + //todo show duplicates quick fix + } + } + } + + return null; + } + + @Nullable + static HighlightInfo checkFileLocation(@NotNull PsiJavaModule element, @NotNull PsiFile file) { + VirtualFile vFile = file.getVirtualFile(); + if (vFile != null) { + VirtualFile root = ProjectFileIndex.SERVICE.getInstance(file.getProject()).getSourceRootForFile(vFile); + if (root != null && !root.equals(vFile.getParent())) { + String message = JavaErrorMessages.message("module.file.wrong.location"); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(range(element)).description(message).create(); + QuickFixAction.registerQuickFixAction(info, new MoveFileFix(vFile, root)); + return info; + } + } + + return null; + } + + private static QuickFixFactory factory() { + return QuickFixFactory.getInstance(); + } + + private static TextRange range(PsiJavaModule module) { + PsiJavaModuleReference name = module.getNameElement(); + return name != null ? new TextRange(module.getTextOffset(), name.getTextRange().getEndOffset()) : module.getTextRange(); + } +} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java new file mode 100644 index 000000000000..e38b044ddf7c --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MoveFileFix.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2016 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 com.intellij.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public class MoveFileFix implements IntentionAction { + private final VirtualFile myFile; + private final VirtualFile myTarget; + + public MoveFileFix(@NotNull VirtualFile file, @NotNull VirtualFile target) { + myFile = file; + myTarget = target; + } + + @Nls + @NotNull + @Override + public String getText() { + return QuickFixBundle.message("move.file.to.source.root.text"); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return true; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (myFile.isValid() && myTarget.isValid()) { + try { + myFile.move(this, myTarget); + } + catch (IOException e) { + throw new IncorrectOperationException("Cannot move '" + myFile.getPath() + "' into '" + myTarget.getPath() + "'", (Throwable)e); + } + } + } + + @Override + public boolean startInWriteAction() { + return true; + } +} \ No newline at end of file diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index feb3c7d28680..4ed5a48d9392 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -385,11 +385,17 @@ cannot.resolve.package=Cannot resolve package {0} override.not.allowed.in.interfaces=@Override is not allowed when implementing interface method wildcard.not.expected=Unexpected wildcard bound.not.expected=Unexpected bound +declaration.not.allowed=Declaration not allowed here underscore.identifier.warn=Use of '_' as an identifier might not be supported in releases after Java 8 underscore.identifier.error=As of Java 9, '_' is a keyword, and may not be used as an identifier underscore.lambda.identifier=Use of '_' as a lambda parameter name is not allowed +module.file.wrong.name=Module declaration should be in a file named 'module-info.java' +module.illegal.name=Illegal module name +module.file.duplicate=Multiple module declarations +module.file.wrong.location=Module declaration should be located in a module's source root + feature.generics=Generics feature.annotations=Annotations feature.static.imports=Static imports @@ -407,5 +413,5 @@ feature.lambda.expressions=Lambda expressions feature.type.annotations=Type annotations feature.type.receivers=Receiver parameters feature.try.with.resources.refs=Resource references +feature.modules=Modules insufficient.language.level={0} are not supported at this language level -declaration.not.allowed=Declaration not allowed here diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting8/ModulesNotSupported.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting8/ModulesNotSupported.java new file mode 100644 index 000000000000..ed3bf668ad18 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting8/ModulesNotSupported.java @@ -0,0 +1,3 @@ +module M { + requires A; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk8Test.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk8Test.java index dc3c391ef218..4c1ab97d7bfd 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk8Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingJdk8Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -49,4 +49,5 @@ public class LightAdvHighlightingJdk8Test extends LightDaemonAnalyzerTestCase { public void testUsedMethodsByMethodReferences() { enableInspectionTool(new UnusedDeclarationInspection()); doTest(true, true); } public void testLambdaExpressions() { doTest(false, true); } public void testUnsupportedFeatures() { doTest(false, false); } -} + public void testModulesNotSupported() { doTest(false, false); } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.java new file mode 100644 index 000000000000..87f3f936866b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2016 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 com.intellij.codeInsight.daemon; + +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.LightPlatformTestCase; +import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public class ModuleHighlightingTest extends LightCodeInsightFixtureTestCase { + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_9; + } + + public void testWrongFileName() { + myFixture.configureByText("M.java", "/* ... */ module M { }"); + myFixture.checkHighlighting(); + } + + public void testFileDuplicate() throws IOException { + myFixture.configureFromExistingVirtualFile(WriteAction.compute(() -> { + VirtualFile file = LightPlatformTestCase.getSourceRoot().createChildDirectory(this, "pkg").createChildData(this, "module-info.java"); + VfsUtil.saveText(file, "module M { }"); + return file; + })); + myFixture.configureByText("module-info.java", "module M { }"); + myFixture.checkHighlighting(); + } + + public void testWrongFileLocation() throws IOException { + myFixture.configureFromExistingVirtualFile(WriteAction.compute(() -> { + VirtualFile file = LightPlatformTestCase.getSourceRoot().createChildDirectory(this, "pkg").createChildData(this, "module-info.java"); + VfsUtil.saveText(file, "module M { }"); + return file; + })); + myFixture.checkHighlighting(); + } +} \ No newline at end of file diff --git a/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java b/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java index 9fc1fc701885..f65c3e866e8d 100644 --- a/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java +++ b/java/testFramework/src/com/intellij/testFramework/fixtures/LightCodeInsightFixtureTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -76,6 +76,17 @@ public abstract class LightCodeInsightFixtureTestCase extends UsefulTestCase { model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.JDK_1_8); } }; + public static final LightProjectDescriptor JAVA_9 = new DefaultLightProjectDescriptor() { + @Override + public Sdk getSdk() { + return IdeaTestUtil.getMockJdk18(); + } + + @Override + public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) { + model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.JDK_1_9); + } + }; public static final LightProjectDescriptor JAVA_LATEST = new DefaultLightProjectDescriptor(); diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index 091b95496c7e..229969706a68 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -223,7 +223,6 @@ orderEntry.fix.add.library.to.classpath=Add library ''{0}'' to classpath orderEntry.fix.family.add.library.to.classpath=Add library to classpath orderEntry.fix.circular.dependency.warning=Adding dependency on module ''{0}'' will introduce circular dependency between modules ''{1}'' and ''{2}''.\nAdd dependency anyway? orderEntry.fix.title.circular.dependency.warning=Circular Dependency Warning -orderEntry.fix.add.junit.jar.to.classpath=Add JUnit to classpath static.import.method.text=Static import method static.import.method.choose.method.to.import=Choose Method to Import @@ -286,4 +285,6 @@ java.8.collections.api.inspection.fix.family.name=Replace with single method cal java.8.collections.api.inspection.fix.text=Replace with ''{0}'' method call wrap.with.optional.parameter.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''java.util.Optional'' -wrap.with.optional.single.parameter.text=Wrap using 'java.util.Optional' \ No newline at end of file +wrap.with.optional.single.parameter.text=Wrap using 'java.util.Optional' + +move.file.to.source.root.text=Move file to a source root