From 922e8725516bad90b8665fe4e6619006df66ade6 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Thu, 12 Jan 2017 15:14:40 +0100 Subject: [PATCH] Provide Annotation class usage linemarker #79, [API] Provide an index with annotated elements stubs #53 --- META-INF/plugin.xml | 1 + .../php/annotation/AnnotationUsageIndex.java | 172 ++++++++++++++++++ .../ClassCompletionProviderAbstract.java | 1 - .../tests/AnnotationUsageIndexTest.java | 25 +++ .../php/annotation/tests/fixtures/usages.php | 36 ++++ 5 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 src/de/espend/idea/php/annotation/AnnotationUsageIndex.java create mode 100644 tests/de/espend/idea/php/annotation/tests/AnnotationUsageIndexTest.java create mode 100644 tests/de/espend/idea/php/annotation/tests/fixtures/usages.php diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index cc6424c2..25b27e42 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -164,6 +164,7 @@ + diff --git a/src/de/espend/idea/php/annotation/AnnotationUsageIndex.java b/src/de/espend/idea/php/annotation/AnnotationUsageIndex.java new file mode 100644 index 00000000..f3227030 --- /dev/null +++ b/src/de/espend/idea/php/annotation/AnnotationUsageIndex.java @@ -0,0 +1,172 @@ +package de.espend.idea.php.annotation; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiRecursiveElementWalkingVisitor; +import com.intellij.util.indexing.*; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.KeyDescriptor; +import com.jetbrains.php.lang.PhpFileType; +import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; +import com.jetbrains.php.lang.psi.PhpFile; +import de.espend.idea.php.annotation.util.AnnotationUtil; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * @author Daniel Espendiller + */ +public class AnnotationUsageIndex extends FileBasedIndexExtension> { + public static final ID> KEY = ID.create("espend.php.annotation.usage"); + private final KeyDescriptor myKeyDescriptor = new EnumeratorStringDescriptor(); + private static StringSetDataExternalizer EXTERNALIZER = new StringSetDataExternalizer(); + + @NotNull + @Override + public ID> getName() { + return KEY; + } + + @NotNull + @Override + public DataIndexer, FileContent> getIndexer() { + return new DataIndexer, FileContent>() { + @NotNull + @Override + public Map> map(@NotNull FileContent inputData) { + final Map> map = new THashMap<>(); + + PsiFile psiFile = inputData.getPsiFile(); + if(!(psiFile instanceof PhpFile)) { + return map; + } + + if(!AnnotationUtil.isValidForIndex(inputData)) { + return map; + } + + psiFile.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if ((element instanceof PhpDocTag)) { + visitPhpDocTag((PhpDocTag) element); + } + + super.visitElement(element); + } + + private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) { + // "@var" and user non related tags dont need an action + if(AnnotationUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) { + return; + } + + String annotationFqnName = StringUtils.stripStart(getClassNameReference(phpDocTag, AnnotationUtil.getUseImportMap(phpDocTag)), "\\"); + + map.put(annotationFqnName, new HashSet<>()); + } + }); + + return map; + } + }; + } + + @NotNull + @Override + public KeyDescriptor getKeyDescriptor() { + return this.myKeyDescriptor; + } + + @NotNull + @Override + public DataExternalizer> getValueExternalizer() { + return EXTERNALIZER; + } + + @NotNull + @Override + public FileBasedIndex.InputFilter getInputFilter() { + return virtualFile -> virtualFile.getFileType() == PhpFileType.INSTANCE; + } + + @Override + public boolean dependsOnFileContent() { + return true; + } + + @Override + public int getVersion() { + return 1; + } + + @Nullable + public static String getClassNameReference(PhpDocTag phpDocTag, Map useImports) { + + if(useImports.size() == 0) { + return null; + } + + String annotationName = phpDocTag.getName(); + if(StringUtils.isBlank(annotationName)) { + return null; + } + + if(annotationName.startsWith("@")) { + annotationName = annotationName.substring(1); + } + + String className = annotationName; + String subNamespaceName = ""; + if(className.contains("\\")) { + className = className.substring(0, className.indexOf("\\")); + subNamespaceName = annotationName.substring(className.length()); + } + + if(!useImports.containsKey(className)) { + return null; + } + + // normalize name + String annotationFqnName = useImports.get(className) + subNamespaceName; + if(!annotationFqnName.startsWith("\\")) { + annotationFqnName = "\\" + annotationFqnName; + } + + return annotationFqnName; + } + + private static class StringSetDataExternalizer implements DataExternalizer> { + public synchronized void save(@NotNull DataOutput out, Set value) throws IOException { + out.writeInt(value.size()); + Iterator var = value.iterator(); + + while(var.hasNext()) { + String s = (String)var.next(); + EnumeratorStringDescriptor.INSTANCE.save(out, s); + } + } + + public synchronized Set read(@NotNull DataInput in) throws IOException { + Set set = new THashSet<>(); + + for(int r = in.readInt(); r > 0; --r) { + set.add(EnumeratorStringDescriptor.INSTANCE.read(in)); + } + + return set; + } + } +} diff --git a/src/de/espend/idea/php/annotation/doctrine/reference/ClassCompletionProviderAbstract.java b/src/de/espend/idea/php/annotation/doctrine/reference/ClassCompletionProviderAbstract.java index 1edfb662..dcb982f2 100644 --- a/src/de/espend/idea/php/annotation/doctrine/reference/ClassCompletionProviderAbstract.java +++ b/src/de/espend/idea/php/annotation/doctrine/reference/ClassCompletionProviderAbstract.java @@ -4,7 +4,6 @@ import com.intellij.psi.PsiReference; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.completion.PhpCompletionUtil; -import com.jetbrains.php.lang.PhpLangUtil; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import de.espend.idea.php.annotation.extension.PhpAnnotationCompletionProvider; import de.espend.idea.php.annotation.extension.PhpAnnotationReferenceProvider; diff --git a/tests/de/espend/idea/php/annotation/tests/AnnotationUsageIndexTest.java b/tests/de/espend/idea/php/annotation/tests/AnnotationUsageIndexTest.java new file mode 100644 index 00000000..9bd1f23b --- /dev/null +++ b/tests/de/espend/idea/php/annotation/tests/AnnotationUsageIndexTest.java @@ -0,0 +1,25 @@ +package de.espend.idea.php.annotation.tests; + +import de.espend.idea.php.annotation.AnnotationUsageIndex; + +import java.io.File; + +/** + * @author Daniel Espendiller + * @see de.espend.idea.php.annotation.AnnotationUsageIndex + */ +public class AnnotationUsageIndexTest extends AnnotationLightCodeInsightFixtureTestCase { + public void setUp() throws Exception { + super.setUp(); + myFixture.copyFileToProject("classes.php"); + myFixture.copyFileToProject("usages.php"); + } + + public String getTestDataPath() { + return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath(); + } + + public void testThatUsagesAreInIndex() { + assertIndexContains(AnnotationUsageIndex.KEY, "Doctrine\\ORM\\Mapping\\Embedded"); + } +} diff --git a/tests/de/espend/idea/php/annotation/tests/fixtures/usages.php b/tests/de/espend/idea/php/annotation/tests/fixtures/usages.php new file mode 100644 index 00000000..90923063 --- /dev/null +++ b/tests/de/espend/idea/php/annotation/tests/fixtures/usages.php @@ -0,0 +1,36 @@ +