[java] using module stubs in analysis/highlighting code

This commit is contained in:
Roman Shevchenko
2016-10-11 16:37:01 +02:00
parent e26eee7bee
commit 893955e1fe
10 changed files with 236 additions and 79 deletions
@@ -20,10 +20,10 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiJavaModuleReference;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.util.CachedValueProvider.Result;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.graph.DFSTBuilder;
@@ -36,7 +36,6 @@ import java.util.*;
import java.util.stream.Collectors;
import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE;
import static com.intellij.psi.SyntaxTraverser.psiTraverser;
import static com.intellij.psi.util.PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT;
public class JavaModuleGraphUtil {
@@ -50,6 +49,13 @@ public class JavaModuleGraphUtil {
return ContainerUtil.find(cycles, set -> set.contains(module));
}
public static boolean exports(@NotNull PsiJavaModule source, @NotNull String packageName, @NotNull PsiJavaModule target) {
Map<String, Set<String>> exports = CachedValuesManager.getCachedValue(source, () ->
Result.create(exportsMap(source), OUT_OF_CODE_BLOCK_MODIFICATION_COUNT));
Set<String> targets = exports.get(packageName);
return targets != null && (targets.isEmpty() || targets.contains(target.getModuleName()));
}
public static boolean reads(@NotNull PsiJavaModule source, @NotNull PsiJavaModule destination) {
Project project = source.getProject();
RequiresGraph graph = CachedValuesManager.getManager(project).getCachedValue(project, () ->
@@ -74,14 +80,11 @@ public class JavaModuleGraphUtil {
if (!projectModules.isEmpty()) {
MultiMap<PsiJavaModule, PsiJavaModule> relations = MultiMap.create();
for (PsiJavaModule module : projectModules) {
for (PsiRequiresStatement statement : psiTraverser().children(module).filter(PsiRequiresStatement.class)) {
Optional.ofNullable(statement.getReferenceElement())
.map(PsiJavaModuleReferenceElement::getReference)
.map(ref -> ref.multiResolve(true))
.map(a -> a.length == 1 ? a[0].getElement() : null)
.map(e -> e instanceof PsiJavaModule ? (PsiJavaModule)e : null)
.filter(projectModules::contains)
.ifPresent(dependency -> relations.putValue(module, dependency));
for (PsiRequiresStatement statement : module.getRequires()) {
PsiJavaModule dependency = PsiJavaModuleReference.resolve(statement, statement.getModuleName(), true);
if (dependency != null && projectModules.contains(dependency)) {
relations.putValue(module, dependency);
}
}
}
@@ -98,6 +101,16 @@ public class JavaModuleGraphUtil {
return Collections.emptyList();
}
private static Map<String, Set<String>> exportsMap(@NotNull PsiJavaModule source) {
Map<String, Set<String>> map = ContainerUtil.newHashMap();
for (PsiExportsStatement statement : source.getExports()) {
String pkg = statement.getPackageName();
List<String> targets = statement.getModuleNames();
map.put(pkg, targets.isEmpty() ? Collections.emptySet() : ContainerUtil.newTroveSet(targets));
}
return map;
}
// Starting from source modules, collects all module dependencies in the project.
// The resulting graph is used for tracing readability.
private static RequiresGraph buildRequiresGraph(Project project) {
@@ -118,28 +131,17 @@ public class JavaModuleGraphUtil {
private static void visit(PsiJavaModule module, MultiMap<PsiJavaModule, PsiJavaModule> relations, Set<String> publicEdges) {
if (!relations.containsKey(module)) {
relations.putValues(module, Collections.emptyList());
for (PsiRequiresStatement statement : psiTraverser().children(module).filter(PsiRequiresStatement.class)) {
Optional.ofNullable(statement.getReferenceElement())
.map(PsiJavaModuleReferenceElement::getReference)
.map(PsiReference::resolve)
.map(e -> e instanceof PsiJavaModule ? (PsiJavaModule)e : null)
.ifPresent(dependency -> {
relations.putValue(module, dependency);
if (isPublic(statement)) publicEdges.add(RequiresGraph.key(dependency, module));
visit(dependency, relations, publicEdges);
});
for (PsiRequiresStatement statement : module.getRequires()) {
PsiJavaModule dependency = PsiJavaModuleReference.resolve(statement, statement.getModuleName(), false);
if (dependency != null) {
relations.putValue(module, dependency);
if (statement.isPublic()) publicEdges.add(RequiresGraph.key(dependency, module));
visit(dependency, relations, publicEdges);
}
}
}
}
private static boolean isPublic(PsiRequiresStatement statement) {
for (PsiElement child = statement.getFirstChild(); child != null; child = child.getNextSibling()) {
if (PsiUtil.isJavaToken(child, JavaTokenType.PUBLIC_KEYWORD)) return true;
if (child instanceof PsiJavaModuleReferenceElement) break;
}
return false;
}
private static class RequiresGraph {
private final Graph<PsiJavaModule> myGraph;
private final Set<String> myPublicEdges;
@@ -118,12 +118,12 @@ public class ModuleHighlightUtil {
List<HighlightInfo> results = ContainerUtil.newSmartList();
checkDuplicateRefs(
psiTraverser().children(module).filter(PsiRequiresStatement.class),
module.getRequires(),
st -> Optional.ofNullable(st.getReferenceElement()).map(PsiJavaModuleReferenceElement::getReferenceText),
"module.duplicate.requires", results);
checkDuplicateRefs(
psiTraverser().children(module).filter(PsiExportsStatement.class),
module.getExports(),
st -> Optional.ofNullable(st.getPackageReference()).map(ModuleHighlightUtil::refText),
"module.duplicate.export", results);
@@ -267,7 +267,7 @@ public class ModuleHighlightUtil {
List<HighlightInfo> results = ContainerUtil.newSmartList();
Set<String> targets = ContainerUtil.newTroveSet();
for (PsiJavaModuleReferenceElement refElement : psiTraverser().children(statement).filter(PsiJavaModuleReferenceElement.class)) {
for (PsiJavaModuleReferenceElement refElement : statement.getModuleReferences()) {
String refText = refElement.getReferenceText();
PsiPolyVariantReference ref = refElement.getReference();
assert ref != null : statement;
@@ -376,7 +376,7 @@ public class ModuleHighlightUtil {
String refModuleName = refModule.getModuleName();
String requiredName = targetModule.getModuleName();
if (!(targetModule instanceof PsiCompiledElement) && !isExported(targetModule, packageName)) {
if (!(targetModule instanceof PsiCompiledElement) && !JavaModuleGraphUtil.exports(targetModule, packageName, refModule)) {
String message = JavaErrorMessages.message("module.package.not.exported", requiredName, packageName, refModuleName);
return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).description(message).create();
}
@@ -389,16 +389,6 @@ public class ModuleHighlightUtil {
return null;
}
private static boolean isExported(PsiJavaModule module, String packageName) {
for (PsiExportsStatement statement : psiTraverser().children(module).filter(PsiExportsStatement.class)) {
String exportedName = Optional.ofNullable(statement.getPackageReference()).map(ModuleHighlightUtil::refText).orElse("");
if (packageName.equals(exportedName)) {
return true;
}
}
return false;
}
private static HighlightInfo moduleResolveError(PsiJavaModuleReferenceElement refElement, PsiPolyVariantReference ref) {
boolean missing = ref.multiResolve(true).length == 0;
String message = JavaErrorMessages.message(missing ? "module.not.found" : "module.not.on.path", refElement.getReferenceText());
@@ -15,8 +15,11 @@
*/
package com.intellij.psi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Represents an {@code exports} statement of a Java module declaration.
*
@@ -26,4 +29,8 @@ public interface PsiExportsStatement extends PsiElement {
PsiExportsStatement[] EMPTY_ARRAY = new PsiExportsStatement[0];
@Nullable PsiJavaCodeReferenceElement getPackageReference();
@Nullable String getPackageName();
@NotNull Iterable<PsiJavaModuleReferenceElement> getModuleReferences();
@NotNull List<String> getModuleNames();
}
@@ -26,4 +26,7 @@ public interface PsiRequiresStatement extends PsiElement {
PsiRequiresStatement[] EMPTY_ARRAY = new PsiRequiresStatement[0];
@Nullable PsiJavaModuleReferenceElement getReferenceElement();
@Nullable String getModuleName();
boolean isPublic();
}
@@ -15,26 +15,70 @@
*/
package com.intellij.psi.impl.compiled;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiExportsStatement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiJavaModuleReferenceElement;
import com.intellij.psi.impl.java.stubs.PsiExportsStatementStub;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class ClsExportsStatementImpl extends ClsRepositoryPsiElement<PsiExportsStatementStub> implements PsiExportsStatement {
private final NotNullLazyValue<PsiJavaCodeReferenceElement> myPackageReference;
private final NotNullLazyValue<Iterable<PsiJavaModuleReferenceElement>> myModuleReferences;
public ClsExportsStatementImpl(PsiExportsStatementStub stub) {
super(stub);
myPackageReference = new AtomicNotNullLazyValue<PsiJavaCodeReferenceElement>() {
@NotNull
@Override
protected PsiJavaCodeReferenceElement compute() {
return new ClsJavaCodeReferenceElementImpl(ClsExportsStatementImpl.this, getStub().getPackageName());
}
};
myModuleReferences = new AtomicNotNullLazyValue<Iterable<PsiJavaModuleReferenceElement>>() {
@NotNull
@Override
protected Iterable<PsiJavaModuleReferenceElement> compute() {
return ContainerUtil.map(getStub().getTargets(), new Function<String, PsiJavaModuleReferenceElement>() {
@Override
public PsiJavaModuleReferenceElement fun(String target) {
return new ClsJavaModuleReferenceElementImpl(ClsExportsStatementImpl.this, target);
}
});
}
};
}
@Override
public PsiJavaCodeReferenceElement getPackageReference() {
return myPackageReference.getValue();
}
@Nullable
@Override
public PsiJavaCodeReferenceElement getPackageReference() {
return null;
public String getPackageName() {
return StringUtil.nullize(getStub().getPackageName());
}
@NotNull
@Override
public Iterable<PsiJavaModuleReferenceElement> getModuleReferences() {
return myModuleReferences.getValue();
}
@NotNull
@Override
public List<String> getModuleNames() {
return getStub().getTargets();
}
@Override
@@ -15,6 +15,8 @@
*/
package com.intellij.psi.impl.compiled;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiJavaModuleReferenceElement;
import com.intellij.psi.PsiRequiresStatement;
@@ -25,14 +27,34 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ClsRequiresStatementImpl extends ClsRepositoryPsiElement<PsiRequiresStatementStub> implements PsiRequiresStatement {
private final NotNullLazyValue<PsiJavaModuleReferenceElement> myModuleReference;
public ClsRequiresStatementImpl(PsiRequiresStatementStub stub) {
super(stub);
myModuleReference = new AtomicNotNullLazyValue<PsiJavaModuleReferenceElement>() {
@NotNull
@Override
protected PsiJavaModuleReferenceElement compute() {
return new ClsJavaModuleReferenceElementImpl(ClsRequiresStatementImpl.this, getStub().getModuleName());
}
};
}
@Nullable
@Override
public PsiJavaModuleReferenceElement getReferenceElement() {
return null;
return myModuleReference.getValue();
}
@Nullable
@Override
public String getModuleName() {
return getStub().getModuleName();
}
@Override
public boolean isPublic() {
return getStub().isPublic();
}
@Override
@@ -16,16 +16,19 @@
package com.intellij.psi.impl.source;
import com.intellij.lang.ASTNode;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiExportsStatement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.impl.java.stubs.PsiExportsStatementStub;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.psi.SyntaxTraverser.psiTraverser;
public class PsiExportsStatementImpl extends JavaStubPsiElement<PsiExportsStatementStub> implements PsiExportsStatement {
public PsiExportsStatementImpl(@NotNull PsiExportsStatementStub stub) {
super(stub, JavaStubElementTypes.EXPORTS_STATEMENT);
@@ -41,6 +44,39 @@ public class PsiExportsStatementImpl extends JavaStubPsiElement<PsiExportsStatem
return PsiTreeUtil.getChildOfType(this, PsiJavaCodeReferenceElement.class);
}
@Nullable
@Override
public String getPackageName() {
PsiExportsStatementStub stub = getGreenStub();
if (stub != null) {
return StringUtil.nullize(stub.getPackageName());
}
else {
PsiJavaCodeReferenceElement ref = getPackageReference();
return ref != null ? PsiNameHelper.getQualifiedClassName(ref.getText(), true) : null;
}
}
@NotNull
@Override
public Iterable<PsiJavaModuleReferenceElement> getModuleReferences() {
return psiTraverser().children(this).filter(PsiJavaModuleReferenceElement.class);
}
@NotNull
@Override
public List<String> getModuleNames() {
PsiExportsStatementStub stub = getGreenStub();
if (stub != null) {
return stub.getTargets();
}
else {
List<String> targets = ContainerUtil.newSmartList();
for (PsiJavaModuleReferenceElement refElement : getModuleReferences()) targets.add(refElement.getReferenceText());
return targets;
}
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
@@ -19,17 +19,29 @@ import com.intellij.core.JavaCoreBundle;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.ParameterizedCachedValue;
import com.intellij.psi.util.ParameterizedCachedValueProvider;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import static com.intellij.openapi.util.Pair.pair;
import static com.intellij.psi.util.PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT;
public class PsiJavaModuleReference extends PsiReferenceBase.Poly<PsiJavaModuleReferenceElement> {
public PsiJavaModuleReference(@NotNull PsiJavaModuleReferenceElement element) {
@@ -75,10 +87,24 @@ public class PsiJavaModuleReference extends PsiReferenceBase.Poly<PsiJavaModuleR
@NotNull
@Override
public ResolveResult[] resolve(@NotNull PsiJavaModuleReference reference, boolean incompleteCode) {
Project project = reference.getProject();
PsiFile file = reference.getElement().getContainingFile();
String moduleName = reference.getCanonicalText();
Collection<PsiJavaModule> modules = findModules(file, moduleName, incompleteCode);
if (!modules.isEmpty()) {
ResolveResult[] result = new ResolveResult[modules.size()];
int i = 0;
for (PsiJavaModule module : modules) result[i++] = new PsiElementResolveResult(module);
return result;
}
else {
return ResolveResult.EMPTY_ARRAY;
}
}
private static Collection<PsiJavaModule> findModules(PsiFile file, String moduleName, boolean incompleteCode) {
Project project = file.getProject();
GlobalSearchScope scope = null;
PsiFile file = reference.getElement().getContainingFile();
if (incompleteCode || file.getOriginalFile() instanceof PsiCompiledFile) {
scope = GlobalSearchScope.allScope(project);
}
@@ -92,18 +118,24 @@ public class PsiJavaModuleReference extends PsiReferenceBase.Poly<PsiJavaModuleR
}
}
if (scope != null) {
JavaFileManager service = JavaFileManager.SERVICE.getInstance(project);
Collection<PsiJavaModule> modules = service.findModules(reference.getCanonicalText(), scope);
if (!modules.isEmpty()) {
ResolveResult[] result = new ResolveResult[modules.size()];
int i = 0;
for (PsiJavaModule module : modules) result[i++] = new PsiElementResolveResult(module);
return result;
}
}
return ResolveResult.EMPTY_ARRAY;
return scope != null ? JavaFileManager.SERVICE.getInstance(project).findModules(moduleName, scope) : Collections.<PsiJavaModule>emptyList();
}
}
private static final Key<ParameterizedCachedValue<PsiJavaModule, Pair<String, Boolean>>> KEY = Key.create("java.module.ref.text.resolve");
@Nullable
public static PsiJavaModule resolve(@NotNull final PsiElement refOwner, String refText, boolean incompleteCode) {
if (StringUtil.isEmpty(refText)) return null;
CachedValuesManager manager = CachedValuesManager.getManager(refOwner.getProject());
return manager.getParameterizedCachedValue(refOwner, KEY, new ParameterizedCachedValueProvider<PsiJavaModule, Pair<String, Boolean>>() {
@Nullable
@Override
public CachedValueProvider.Result<PsiJavaModule> compute(Pair<String, Boolean> p) {
Collection<PsiJavaModule> modules = Resolver.findModules(refOwner.getContainingFile(), p.first, p.second);
PsiJavaModule module = modules.size() == 1 ? modules.iterator().next() : null;
return CachedValueProvider.Result.create(module, OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}, false, pair(refText, incompleteCode));
}
}
@@ -16,13 +16,11 @@
package com.intellij.psi.impl.source;
import com.intellij.lang.ASTNode;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiJavaModuleReferenceElement;
import com.intellij.psi.PsiRequiresStatement;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.impl.java.stubs.PsiRequiresStatementStub;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,6 +39,34 @@ public class PsiRequiresStatementImpl extends JavaStubPsiElement<PsiRequiresStat
return PsiTreeUtil.getChildOfType(this, PsiJavaModuleReferenceElement.class);
}
@Nullable
@Override
public String getModuleName() {
PsiRequiresStatementStub stub = getGreenStub();
if (stub != null) {
return stub.getModuleName();
}
else {
PsiJavaModuleReferenceElement refElement = getReferenceElement();
return refElement != null ? refElement.getReferenceText() : null;
}
}
@Override
public boolean isPublic() {
PsiRequiresStatementStub stub = getGreenStub();
if (stub != null) {
return stub.isPublic();
}
else {
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
if (PsiUtil.isJavaToken(child, JavaTokenType.PUBLIC_KEYWORD)) return true;
if (child instanceof PsiJavaModuleReferenceElement) break;
}
return false;
}
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
@@ -15,10 +15,8 @@
*/
package com.intellij.codeInsight.daemon
import com.intellij.psi.PsiJavaModule
import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase
import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.*
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
import org.assertj.core.api.Assertions.assertThat
class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
@@ -54,7 +52,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
<error descr="Duplicate uses: pkg.main.C">uses pkg. main . /*...*/ C;</error>
provides pkg .main .C with pkg.main.Impl;
<error descr="Duplicate provides: pkg.main.C / pkg.main.Impl">provides pkg.main.C with pkg. main. Impl;</error>
}""".trimIndent(), true)
}""".trimIndent())
}
fun testUnusedStatements() {
@@ -63,7 +61,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
highlight("""
module M {
provides pkg.main.<warning descr="Service interface provided but not exported or used">C</warning> with pkg.main.Impl;
}""".trimIndent(), true)
}""".trimIndent())
}
fun testRequires() {
@@ -74,7 +72,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
requires <error descr="Cyclic dependence: M1">M1</error>;
requires <error descr="Cyclic dependence: M1, M2">M2</error>;
requires <error descr="Module is not in dependencies: M3">M3</error>;
}""".trimIndent(), true)
}""".trimIndent())
}
fun testExports() {
@@ -137,7 +135,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
fun testPackageAccessibility() {
addFile("module-info.java", "module M { requires M2; requires M6; }")
addFile("module-info.java", "module M2 { exports pkg.m2; }", M2)
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)
@@ -159,17 +157,14 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
import static <error descr="The module 'M2' does not export the package 'pkg.m2.impl' to the module 'M'">pkg.m2.impl.C2Impl</error>.make;
class C { }
""".trimIndent(), true)
""".trimIndent())
}
//<editor-fold desc="Helpers.">
private fun highlight(text: String, filter: Boolean = false) = highlight("module-info.java", text, filter)
private fun highlight(text: String) = highlight("module-info.java", text)
private fun highlight(path: String, text: String, filter: Boolean = false) {
private fun highlight(path: String, text: String) {
myFixture.configureFromExistingVirtualFile(addFile(path, text))
if (filter) {
(myFixture as CodeInsightTestFixtureImpl).setVirtualFileFilter { it.name != PsiJavaModule.MODULE_INFO_FILE }
}
myFixture.checkHighlighting()
}