diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java index 130fccc34b23..525e5fbad288 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java @@ -53,6 +53,7 @@ import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; import org.jetbrains.plugins.groovy.lang.psi.stubs.GrFileStub; import org.jetbrains.plugins.groovy.lang.psi.stubs.GrPackageDefinitionStub; +import org.jetbrains.plugins.groovy.lang.resolve.ImplicitImportsKt; import org.jetbrains.plugins.groovy.lang.resolve.MethodTypeInferencer; import org.jetbrains.plugins.groovy.lang.resolve.PackageSkippingProcessor; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; @@ -165,7 +166,7 @@ public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile { if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.SIMPLE, null)) return false; if (!processDeclarationsInPackage(processor, state, lastParent, place)) return false; if (!processImports(processor, state, lastParent, place, importStatements, ImportKind.ON_DEMAND, null)) return false; - if (!GroovyImportHelper.processImplicitImports(processor, state, lastParent, place, this)) return false; + if (!ImplicitImportsKt.processImplicitImports(processor, state, lastParent, place, this)) return false; if (ResolveUtil.shouldProcessPackages(classHint)) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyImportHelper.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyImportHelper.java index cd3f90ee7b3f..0290e5d49b53 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyImportHelper.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyImportHelper.java @@ -15,13 +15,10 @@ */ package org.jetbrains.plugins.groovy.lang.psi.impl; -import com.intellij.psi.*; -import com.intellij.psi.scope.ElementClassHint; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,11 +26,9 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; import org.jetbrains.plugins.groovy.lang.resolve.GrImportContributor; -import org.jetbrains.plugins.groovy.lang.resolve.PackageSkippingProcessor; -import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; +import org.jetbrains.plugins.groovy.lang.resolve.ImportType; import java.util.LinkedHashSet; -import java.util.List; /** * @author Max Medvedev @@ -75,7 +70,7 @@ public class GroovyImportHelper { for (GrImportContributor contributor : GrImportContributor.EP_NAME.getExtensions()) { result.addAll(ContainerUtil.mapNotNull( contributor.getImports(file), - i -> i.getStar() && !i.getStatic() ? i.getName() : null + i -> i.getType() == ImportType.STAR ? i.getName() : null )); } @@ -98,38 +93,6 @@ public class GroovyImportHelper { return true; } - public static boolean processImplicitImports(@NotNull PsiScopeProcessor processor, - @NotNull ResolveState state, - @Nullable PsiElement lastParent, - @NotNull PsiElement place, - @NotNull GroovyFile file) { - if (!ResolveUtil.shouldProcessClasses(processor.getHint(ElementClassHint.KEY))) return true; - - JavaPsiFacade facade = JavaPsiFacade.getInstance(file.getProject()); - - final PsiScopeProcessor packageSkipper = new PackageSkippingProcessor(processor); - - for (final String implicitlyImported : getImplicitlyImportedPackages(file)) { - PsiPackage aPackage = facade.findPackage(implicitlyImported); - if (aPackage == null) continue; - - if (!aPackage.processDeclarations(packageSkipper, state, lastParent, place)) { - return false; - } - } - - List implicitlyImportedClasses = CachedValuesManager.getCachedValue(file, () -> { - GlobalSearchScope scope = file.getResolveScope(); - List classes = ContainerUtil.mapNotNull(GroovyFileBase.IMPLICITLY_IMPORTED_CLASSES, s -> facade.findClass(s, scope)); - return CachedValueProvider.Result.create(classes, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); - }); - - for (PsiClass clazz : implicitlyImportedClasses) { - if (!ResolveUtil.processElement(processor, clazz, state)) return false; - } - return true; - } - @Nullable private static ImportKind getImportKind(GrImportStatement statement) { if (statement.isOnDemand() && !statement.isAliasedImport()) return ImportKind.ON_DEMAND; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/FromStringHintProcessor.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/FromStringHintProcessor.java index 4b968b5612da..928348997b22 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/FromStringHintProcessor.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/FromStringHintProcessor.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.GroovyLanguage; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; -import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyImportHelper; +import org.jetbrains.plugins.groovy.lang.resolve.ImplicitImportsKt; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; import java.util.List; @@ -96,7 +96,7 @@ class FromStringLightElement extends LightElement { } } - if (!GroovyImportHelper.processImplicitImports(processor, state, lastParent, place, myFile)) { + if (!ImplicitImportsKt.processImplicitImports(processor, state, lastParent, place, myFile)) { return false; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/imports.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/DefaultImportContributor.kt similarity index 56% rename from plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/imports.kt rename to plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/DefaultImportContributor.kt index 6d7b899f8699..a7ad254ebf07 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/imports.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/DefaultImportContributor.kt @@ -17,25 +17,23 @@ package org.jetbrains.plugins.groovy.lang.resolve import org.jetbrains.plugins.groovy.lang.psi.GroovyFile -/** - * regular: class - * static: class member - * star: classes in package - * static star: members of class - */ -class Import( - val name: String, - val static: Boolean = false, - val star: Boolean = false +private val classes = listOf( + Import("java.math.BigInteger"), + Import("java.math.BigDecimal") ) -abstract class GrImportContributorBase : GrImportContributor { +private val packages = listOf( + Import("java.lang", ImportType.STAR), + Import("java.util", ImportType.STAR), + Import("java.io", ImportType.STAR), + Import("java.net", ImportType.STAR), + Import("groovy.lang", ImportType.STAR), + Import("groovy.util", ImportType.STAR) +) - abstract fun appendImplicitlyImportedPackages(file: GroovyFile): List +private val imports = classes + packages - final override fun getImports(file: GroovyFile): Collection { - return appendImplicitlyImportedPackages(file).map { - Import(name = it, star = true) - } - } +class DefaultImportContributor : GrImportContributor { + + override fun getImports(file: GroovyFile): Collection = imports } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/implicitImports.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/implicitImports.kt new file mode 100644 index 000000000000..96b31e17c5a1 --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/implicitImports.kt @@ -0,0 +1,107 @@ +/* + * 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 org.jetbrains.plugins.groovy.lang.resolve + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.ResolveState +import com.intellij.psi.scope.PsiScopeProcessor +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile + +open class Import( + val name: String, + val type: ImportType = ImportType.REGULAR +) + +enum class ImportType { + /** + * Class + */ + REGULAR, + /** + * Class member + */ + STATIC, + /** + * Classes of package + */ + STAR, + /** + * Members of class + */ + STATIC_STAR +} + +abstract class GrImportContributorBase : GrImportContributor { + + abstract fun appendImplicitlyImportedPackages(file: GroovyFile): List + + final override fun getImports(file: GroovyFile): Collection { + return appendImplicitlyImportedPackages(file).map { + Import(it, ImportType.STAR) + } + } +} + +fun getImplicitImports(file: GroovyFile): Collection = GrImportContributor.EP_NAME.extensions.fold(emptyList()) { acc, ext -> + acc + ext.getImports(file) +} + +fun processImplicitImports(processor: PsiScopeProcessor, + state: ResolveState, + lastParent: PsiElement?, + place: PsiElement, + file: GroovyFile): Boolean { + val hint = processor.getHint(com.intellij.psi.scope.ElementClassHint.KEY) + val facade = JavaPsiFacade.getInstance(file.project) + val packageSkipper = PackageSkippingProcessor(processor) + + loop@for (implicitImport in getImplicitImports(file)) { + when (implicitImport.type) { + ImportType.REGULAR -> { + if (!ResolveUtil.shouldProcessClasses(hint)) continue@loop + val clazz = facade.findClass(implicitImport.name, file.resolveScope) ?: continue@loop + if (!ResolveUtil.processElement(processor, clazz, state)) return false + } + ImportType.STATIC -> { + val className = StringUtil.getPackageName(implicitImport.name) + val memberName = StringUtil.getShortName(implicitImport.name) + if (StringUtil.isEmptyOrSpaces(className) || StringUtil.isEmptyOrSpaces(memberName)) continue@loop + val clazz = facade.findClass(className, file.resolveScope) ?: continue@loop + if (ResolveUtil.shouldProcessMethods (hint)) { + for (method in clazz.findMethodsByName(memberName, true)) { + if (!ResolveUtil.processElement(processor, method, state)) return false + } + } + if (ResolveUtil.shouldProcessProperties(hint)) { + val field = clazz.findFieldByName(memberName, true) ?: continue@loop + if (!ResolveUtil.processElement(processor, field, state)) return false + } + } + ImportType.STAR -> { + val pckg = facade.findPackage(implicitImport.name) ?: continue@loop + if (!pckg.processDeclarations(packageSkipper, state, lastParent, place)) return false + } + ImportType.STATIC_STAR -> { + val clazz = facade.findClass(implicitImport.name, file.resolveScope) ?: continue@loop + if (!clazz.processDeclarations(processor, state, lastParent, place)) return false + } + } + } + + return true +} diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 54c190e7cf1c..dee1183f4f9a 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -247,6 +247,7 @@ + diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrImportContributorTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrImportContributorTest.groovy new file mode 100644 index 000000000000..c53b25bf6fd5 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrImportContributorTest.groovy @@ -0,0 +1,92 @@ +/* + * 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 org.jetbrains.plugins.groovy.lang.resolve + +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.PlatformTestUtil +import groovy.transform.CompileStatic +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase +import org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection + +@CompileStatic +class GrImportContributorTest extends LightGroovyTestCase { + + LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + @Override + public void setUp() throws Exception { + super.setUp(); + fixture.enableInspections(GrUnresolvedAccessInspection) + fixture.addClass ''' +package foo.bar; +public class MyClass { + public static Object BAR = null; + public static void foo() {} +} +''' + } + + void 'test regular import'() { + PlatformTestUtil.registerExtension(GrImportContributor.EP_NAME, { + [new Import("foo.bar.MyClass", ImportType.REGULAR)] + } as GrImportContributor, testRootDisposable) + fixture.with { + configureByText('a.groovy', 'new MyClass()') + checkHighlighting() + } + } + + void 'test static import method'() { + PlatformTestUtil.registerExtension(GrImportContributor.EP_NAME, { + [new Import("foo.bar.MyClass.foo", ImportType.STATIC)] + } as GrImportContributor, testRootDisposable) + fixture.with { + configureByText('a.groovy', 'foo()') + checkHighlighting() + } + } + + void 'test static import field'() { + PlatformTestUtil.registerExtension(GrImportContributor.EP_NAME, { + [new Import("foo.bar.MyClass.BAR", ImportType.STATIC)] + } as GrImportContributor, testRootDisposable) + fixture.with { + configureByText('a.groovy', 'println BAR') + checkHighlighting() + } + } + + void 'test star import'() { + PlatformTestUtil.registerExtension(GrImportContributor.EP_NAME, { + [new Import("foo.bar", ImportType.STAR)] + } as GrImportContributor, testRootDisposable) + fixture.with { + configureByText('a.groovy', 'new MyClass()') + checkHighlighting() + } + } + + void 'test static star import'() { + PlatformTestUtil.registerExtension(GrImportContributor.EP_NAME, { + [new Import("foo.bar.MyClass", ImportType.STATIC_STAR)] + } as GrImportContributor, testRootDisposable) + fixture.with { + configureByText('a.groovy', 'println foo()\n println BAR') + checkHighlighting() + } + } +}