/*
* Copyright 2000-2017 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.codeInsight.daemon.impl.JavaHighlightInfoTypes
import com.intellij.codeInspection.deprecation.DeprecationInspection
import com.intellij.openapi.util.TextRange
import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.*
import org.assertj.core.api.Assertions.assertThat
class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
override fun setUp() {
super.setUp()
addFile("module-info.java", "module M2 { }", M2)
addFile("module-info.java", "module M3 { }", M3)
}
fun testPackageStatement() {
highlight("package pkg;")
highlight("""
package pkg;
module M { }""".trimIndent())
fixes("package pkg;\nmodule M { }", "DeleteElementFix")
}
fun testSoftKeywords() {
addFile("pkg/module/C.java", "package pkg.module;\npublic class C { }")
myFixture.configureByText("module-info.java", "module M { exports pkg.module; }")
val keywords = myFixture.doHighlighting().filter { it.type == JavaHighlightInfoTypes.JAVA_KEYWORD }
assertEquals(2, keywords.size)
assertEquals(TextRange(0, 6), TextRange(keywords[0].startOffset, keywords[0].endOffset))
assertEquals(TextRange(11, 18), TextRange(keywords[1].startOffset, keywords[1].endOffset))
}
fun testWrongFileName() {
highlight("M.java", """/* ... */ module M { }""")
}
fun testFileDuplicate() {
addFile("pkg/module-info.java", """module M.bis { }""")
highlight("""module M { }""")
}
fun testFileDuplicateInTestRoot() {
addTestFile("module-info.java", """module M.test { }""")
highlight("""module M { }""")
}
fun testWrongFileLocation() {
highlight("pkg/module-info.java", """module M { }""")
}
fun testIncompatibleModifiers() {
highlight("""
module M {
requires static transitive M2;
requires private volatile M3;
}""".trimIndent())
}
fun testAnnotations() {
highlight("""@Deprecated @Override module M { }""")
}
fun testDuplicateStatements() {
addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
highlight("""
module M {
requires M2;
requires M2;
exports pkg.main;
exports pkg. main;
opens pkg.main;
opens pkg. main;
uses pkg.main.C;
uses pkg. main . /*...*/ C;
provides pkg .main .C with pkg.main.Impl;
provides pkg.main.C with pkg. main. Impl;
}""".trimIndent())
}
fun testUnusedStatements() {
addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
highlight("""
module M {
provides pkg.main.C with pkg.main.Impl;
}""".trimIndent())
}
fun testRequires() {
addFile("module-info.java", "module M2 { requires M1 }", M2)
highlight("""
module M1 {
requires M.missing;
requires M1;
requires M2;
requires M3;
requires lib.auto;
requires lib.multi.release;
}""".trimIndent())
}
fun testExports() {
addFile("pkg/empty/package-info.java", "package pkg.empty;")
addFile("pkg/main/C.java", "package pkg.main;\nclass C { }")
addFile("pkg/other/C.groovy", "package pkg.other\nclass C { }")
highlight("""
module M {
exports pkg.missing.unknown;
exports pkg.empty;
exports pkg.main to M.missing, M2, M2;
}""".trimIndent())
}
fun testOpens() {
addFile("pkg/empty/package-info.java", "package pkg.empty;")
addFile("pkg/main/C.java", "package pkg.main;\nclass C { }")
addFile("pkg/other/C.groovy", "package pkg.other\nclass C { }")
highlight("""
module M {
opens pkg.missing.unknown;
opens pkg.empty;
opens pkg.main to M.missing, M2, M2;
}""".trimIndent())
}
fun testWeakModule() {
highlight("""open module M { opens pkg.missing; }""")
}
fun testUses() {
addFile("pkg/main/C.java", "package pkg.main;\nclass C { void m(); }")
addFile("pkg/main/O.java", "package pkg.main;\npublic class O {\n public class I { void m(); }\n}")
addFile("pkg/main/E.java", "package pkg.main;\npublic enum E { }")
highlight("""
module M {
uses pkg.main;
uses pkg.main.X;
uses pkg.main.C;
uses pkg.main.E;
uses pkg.main.O.I;
uses pkg.main.O.I.m;
}""".trimIndent())
}
fun testProvides() {
addFile("pkg/main/C.java", "package pkg.main;\npublic interface C { }")
addFile("pkg/main/CT.java", "package pkg.main;\npublic interface CT { }")
addFile("pkg/main/Impl1.java", "package pkg.main;\nclass Impl1 { }")
addFile("pkg/main/Impl2.java", "package pkg.main;\npublic class Impl2 { }")
addFile("pkg/main/Impl3.java", "package pkg.main;\npublic abstract class Impl3 implements C { }")
addFile("pkg/main/Impl4.java", "package pkg.main;\npublic class Impl4 implements C {\n public Impl4(String s) { }\n}")
addFile("pkg/main/Impl5.java", "package pkg.main;\npublic class Impl5 implements C {\n protected Impl5() { }\n}")
addFile("pkg/main/Impl6.java", "package pkg.main;\npublic class Impl6 implements C { }")
addFile("pkg/main/Impl7.java", "package pkg.main;\npublic class Impl7 {\n public static void provider();\n}")
addFile("pkg/main/Impl8.java", "package pkg.main;\npublic class Impl8 {\n public static C provider();\n}")
addFile("pkg/main/Impl9.java", "package pkg.main;\npublic class Impl9 {\n public class Inner implements C { }\n}")
addFile("pkg/main/Impl10.java", "package pkg.main;\npublic class Impl10 implements CT {\n private Impl10() { }\n public static CT provider();\n}")
addFile("module-info.java", "module M2 {\n exports pkg.m2;\n}", M2)
addFile("pkg/m2/C.java", "package pkg.m2;\npublic class C { }", M2)
addFile("pkg/m2/Impl.java", "package pkg.m2;\npublic class Impl extends C { }", M2)
highlight("""
module M {
requires M2;
provides pkg.main.C with pkg.main.NoImpl;
provides pkg.main.C with pkg.main.Impl1;
provides pkg.main.C with pkg.main.Impl2;
provides pkg.main.C with pkg.main.Impl3;
provides pkg.main.C with pkg.main.Impl4;
provides pkg.main.C with pkg.main.Impl5;
provides pkg.main.C with pkg.main.Impl6, pkg.main.Impl6;
provides pkg.main.C with pkg.main.Impl7;
provides pkg.main.C with pkg.main.Impl8;
provides pkg.main.C with pkg.main.Impl9.Inner;
provides pkg.main.CT with pkg.main.Impl10;
provides pkg.m2.C with pkg.m2.Impl;
}""".trimIndent())
}
fun testModuleRefFixes() {
fixes("module M { requires M.missing; }")
fixes("module M { requires M3; }", "AddModuleDependencyFix")
}
fun testPackageRefFixes() {
fixes("module M { exports pkg.missing; }")
addFile("pkg/m3/C3.java", "package pkg.m3;\npublic class C3 { }", M3)
fixes("module M { exports pkg.m3; }")
}
fun testClassRefFixes() {
addFile("pkg/m3/C3.java", "package pkg.m3;\npublic class C3 { }", M3)
fixes("module M { uses pkg.m3.C3; }", "AddModuleDependencyFix")
}
fun testPackageAccessibility() {
addFile("module-info.java", "module M { requires M2; requires M6; requires lib.named; requires lib.auto; }")
addFile("module-info.java", "module M2 { exports pkg.m2; exports pkg.m2.impl to close.friends.only; }", M2)
addFile("pkg/m2/C2.java", "package pkg.m2;\npublic class C2 { }", M2)
addFile("pkg/m2/impl/C2Impl.java", "package pkg.m2.impl;\nimport pkg.m2.C2;\npublic class C2Impl { public static C2 make() {} }", M2)
addFile("pkg/m4/C4.java", "package pkg.m4;\npublic class C4 { }", M4)
addFile("module-info.java", "module M5 { exports pkg.m5; }", M5)
addFile("pkg/m5/C5.java", "package pkg.m5;\npublic class C5 { }", M5)
addFile("module-info.java", "module M6 { requires transitive M7; }", M6)
addFile("module-info.java", "module M7 { exports pkg.m7; }", M7)
addFile("pkg/m7/C7.java", "package pkg.m7;\npublic class C7 { }", M7)
highlight("test.java", """
import pkg.m2.C2;
import pkg.m2.*;
import pkg.m2.impl.C2Impl;
import pkg.m2.impl.*;
import pkg.m4.C4;
import pkg.m5.C5;
import pkg.m7.C7;
import pkg.lib1.LC1;
import pkg.lib1.impl.LC1Impl;
import pkg.lib1.impl.*;
import pkg.lib2.LC2;
import pkg.lib2.impl.LC2Impl;
import static pkg.m2.impl.C2Impl.make;
/** See also {@link C2Impl#make} */
class C {{
C2Impl.make();
pkg.m2.impl.C2Impl.make();
}}
""".trimIndent())
}
fun testPackageAccessibilityInNonModularTest() {
addFile("module-info.java", "module M { }")
addFile("module-info.java", "module M2 { exports pkg.m2; exports pkg.m2.impl to close.friends.only; }", M2)
addFile("pkg/m2/C2.java", "package pkg.m2;\npublic class C2 { }", M2)
addFile("pkg/m2/impl/C2Impl.java", "package pkg.m2.impl;\nimport pkg.m2.C2;\npublic class C2Impl { public static C2 make() {} }", M2)
addFile("pkg/m4/C4.java", "package pkg.m4;\npublic class C4 { }", M4)
addFile("module-info.java", "module M5 { exports pkg.m5; }", M5)
addFile("pkg/m5/C5.java", "package pkg.m5;\npublic class C5 { }", M5)
addFile("module-info.java", "module M6 { requires transitive M7; }", M6)
addFile("module-info.java", "module M7 { exports pkg.m7; }", M7)
addFile("pkg/m7/C7.java", "package pkg.m7;\npublic class C7 { }", M7)
highlight("test.java", """
import pkg.m2.C2;
import pkg.m2.*;
import pkg.m2.impl.C2Impl;
import pkg.m2.impl.*;
import pkg.m4.C4;
import pkg.m5.C5;
import pkg.m7.C7;
import pkg.lib1.LC1;
import pkg.lib1.impl.LC1Impl;
import pkg.lib1.impl.*;
import pkg.lib2.LC2;
import pkg.lib2.impl.LC2Impl;
import static pkg.m2.impl.C2Impl.make;
/** See also {@link C2Impl#make} */
class C {{
C2Impl.make();
pkg.m2.impl.C2Impl.make();
}}
""".trimIndent(), true)
}
fun testPackageAccessibilityInModularTest() {
addTestFile("module-info.java", "module M { requires M2; requires M6; requires lib.named; requires lib.auto; }")
addFile("module-info.java", "module M2 { exports pkg.m2; exports pkg.m2.impl to close.friends.only; }", M2)
addFile("pkg/m2/C2.java", "package pkg.m2;\npublic class C2 { }", M2)
addFile("pkg/m2/impl/C2Impl.java", "package pkg.m2.impl;\nimport pkg.m2.C2;\npublic class C2Impl { public static C2 make() {} }", M2)
addFile("pkg/m4/C4.java", "package pkg.m4;\npublic class C4 { }", M4)
addFile("module-info.java", "module M5 { exports pkg.m5; }", M5)
addFile("pkg/m5/C5.java", "package pkg.m5;\npublic class C5 { }", M5)
addFile("module-info.java", "module M6 { requires transitive M7; }", M6)
addFile("module-info.java", "module M7 { exports pkg.m7; }", M7)
addFile("pkg/m7/C7.java", "package pkg.m7;\npublic class C7 { }", M7)
highlight("test.java", """
import pkg.m2.C2;
import pkg.m2.*;
import pkg.m2.impl.C2Impl;
import pkg.m2.impl.*;
import pkg.m4.C4;
import pkg.m5.C5;
import pkg.m7.C7;
import pkg.lib1.LC1;
import pkg.lib1.impl.LC1Impl;
import pkg.lib1.impl.*;
import pkg.lib2.LC2;
import pkg.lib2.impl.LC2Impl;
import static pkg.m2.impl.C2Impl.make;
/** See also {@link C2Impl#make} */
class C {{
C2Impl.make();
pkg.m2.impl.C2Impl.make();
}}
""".trimIndent(), true)
}
fun testLinearModuleGraphBug() {
addFile("module-info.java", "module M6 { requires M7; }", M6)
addFile("module-info.java", "module M7 { }", M7)
highlight("module M { requires M6; }")
}
fun testDeprecations() {
myFixture.enableInspections(DeprecationInspection())
addFile("module-info.java", "@Deprecated module M2 { }", M2)
highlight("""module M { requires M2; }""")
}
fun testPackageConflicts() {
addFile("pkg/collision2/C2.java", "package pkg.collision2;\npublic class C2 { }", M2)
addFile("pkg/collision4/C4.java", "package pkg.collision4;\npublic class C4 { }", M4)
addFile("pkg/collision7/C7.java", "package pkg.collision7;\npublic class C7 { }", M7)
addFile("module-info.java", "module M2 { exports pkg.collision2; }", M2)
addFile("module-info.java", "module M4 { exports pkg.collision4 to M88; }", M4)
addFile("module-info.java", "module M6 { requires transitive M7; }", M6)
addFile("module-info.java", "module M7 { exports pkg.collision7 to M6; }", M7)
addFile("module-info.java", "module M { requires M2; requires M4; requires M6; requires lib.auto; }")
highlight("test1.java", """package pkg.collision2;""")
highlight("test2.java", """package pkg.collision4;""")
highlight("test3.java", """package pkg.collision7;""")
highlight("test4.java", """package java.util;""")
highlight("test5.java", """package pkg.lib2;""")
}
fun testClashingReads1() {
addFile("pkg/collision/C2.java", "package pkg.collision;\npublic class C2 { }", M2)
addFile("pkg/collision/C7.java", "package pkg.collision;\npublic class C7 { }", M7)
addFile("module-info.java", "module M2 { exports pkg.collision; }", M2)
addFile("module-info.java", "module M6 { requires transitive M7; }", M6)
addFile("module-info.java", "module M7 { exports pkg.collision; }", M7)
highlight("""
module M {
requires M2;
requires M6;
}""".trimIndent())
}
fun testClashingReads2() {
addFile("pkg/collision/C2.java", "package pkg.collision;\npublic class C2 { }", M2)
addFile("pkg/collision/C4.java", "package pkg.collision;\npublic class C4 { }", M4)
addFile("module-info.java", "module M2 { exports pkg.collision; }", M2)
addFile("module-info.java", "module M4 { exports pkg.collision to somewhere; }", M4)
highlight("module M { requires M2; requires M4; }")
}
fun testClashingReads3() {
addFile("pkg/lib2/C2.java", "package pkg.lib2;\npublic class C2 { }", M2)
addFile("module-info.java", "module M2 { exports pkg.lib2; }", M2)
highlight("""
module M {
requires M2;
requires lib.auto;
}""".trimIndent())
}
//
private fun highlight(text: String) = highlight("module-info.java", text)
private fun highlight(path: String, text: String, isTest: Boolean = false) {
myFixture.configureFromExistingVirtualFile(if (isTest) addTestFile(path, text) else addFile(path, text))
myFixture.checkHighlighting()
}
private fun fixes(text: String, vararg fixes: String) {
myFixture.configureByText("module-info.java", text)
assertThat(myFixture.getAllQuickFixes().map { it.javaClass.simpleName }).containsExactlyInAnyOrder(*fixes)
}
//
}