Skip to content

Commit

Permalink
Provide Annotation class usage linemarker #79, [API] Provide an index…
Browse files Browse the repository at this point in the history
… with annotated elements stubs #53
  • Loading branch information
Haehnchen committed Sep 15, 2017
1 parent 34acbf2 commit 9d0a74c
Show file tree
Hide file tree
Showing 11 changed files with 502 additions and 1 deletion.
3 changes: 3 additions & 0 deletions META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@

<projectService serviceImplementation="de.espend.idea.php.annotation.Settings"/>
<fileBasedIndex implementation="de.espend.idea.php.annotation.AnnotationStubIndex"/>
<fileBasedIndex implementation="de.espend.idea.php.annotation.AnnotationUsageIndex"/>

<gotoDeclarationHandler implementation="de.espend.idea.php.annotation.navigation.AnnotationGoToDeclarationHandler"/>
<completion.contributor language="PHP" implementationClass="de.espend.idea.php.annotation.completion.AnnotationCompletionContributor"/>
Expand All @@ -176,6 +177,8 @@

<annotator language="PHP" implementationClass="de.espend.idea.php.annotation.annotator.AnnotationDocTagAnnotator"/>

<codeInsight.lineMarkerProvider language="PHP" implementationClass="de.espend.idea.php.annotation.navigation.AnnotationUsageLineMarkerProvider"/>

<localInspection groupPath="PHP" shortName="AnnotationMissingUseInspection" displayName="Missing Import"
groupName="Annotation"
enabledByDefault="true"
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ use Doctrine\ORM\Mapping as ORM;
class Foo {}
```

#### Class LineMarker

LineMarker which provide navigation to annotation class usages

```php
namespace Doctrine\ORM\Mapping;

/**
* @Annotation
*/
final class Entity implements Annotation
{
}
```

Targeting

```php

/**
* @ORM\Entity()
*/
```

### Annotation Class Detection

* Every class with `@Annotation` inside class doc block is detected on file indexing
Expand Down
111 changes: 111 additions & 0 deletions src/de/espend/idea/php/annotation/AnnotationUsageIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package de.espend.idea.php.annotation;

import com.intellij.psi.PsiFile;
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.psi.PhpFile;
import de.espend.idea.php.annotation.util.AnnotationUtil;
import de.espend.idea.php.annotation.util.PhpDocTagAnnotationRecursiveElementWalkingVisitor;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;

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 <daniel@espendiller.net>
*/
public class AnnotationUsageIndex extends FileBasedIndexExtension<String, Set<String>> {
public static final ID<String, Set<String>> KEY = ID.create("espend.php.annotation.usage");
private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor();
private static StringSetDataExternalizer EXTERNALIZER = new StringSetDataExternalizer();

@NotNull
@Override
public ID<String, Set<String>> getName() {
return KEY;
}

@NotNull
@Override
public DataIndexer<String, Set<String>, FileContent> getIndexer() {
return inputData -> {
final Map<String, Set<String>> map = new THashMap<>();

PsiFile psiFile = inputData.getPsiFile();
if(!(psiFile instanceof PhpFile)) {
return map;
}

if(!AnnotationUtil.isValidForIndex(inputData)) {
return map;
}

psiFile.accept(new PhpDocTagAnnotationRecursiveElementWalkingVisitor(pair -> {
map.put(pair.getFirst(), new HashSet<>());
return true;
}));

return map;
};
}

@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return this.myKeyDescriptor;
}

@NotNull
@Override
public DataExternalizer<Set<String>> 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;
}

private static class StringSetDataExternalizer implements DataExternalizer<Set<String>> {
public synchronized void save(@NotNull DataOutput out, Set<String> 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<String> read(@NotNull DataInput in) throws IOException {
Set<String> set = new THashSet<>();

for(int r = in.readInt(); r > 0; --r) {
set.add(EnumeratorStringDescriptor.INSTANCE.read(in));
}

return set;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package de.espend.idea.php.annotation.navigation;

import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.php.PhpIcons;
import com.jetbrains.php.lang.PhpFileType;
import com.jetbrains.php.lang.PhpLanguage;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import de.espend.idea.php.annotation.AnnotationUsageIndex;
import de.espend.idea.php.annotation.util.AnnotationUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class AnnotationUsageLineMarkerProvider implements LineMarkerProvider {
@Nullable
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) {
return null;
}

@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> psiElements, @NotNull Collection<LineMarkerInfo> results) {
for(PsiElement psiElement: psiElements) {
if(!getClassNamePattern().accepts(psiElement)) {
continue;
}

PsiElement phpClass = psiElement.getContext();
if(!(phpClass instanceof PhpClass) || !AnnotationUtil.isAnnotationClass((PhpClass) phpClass)) {
return;
}

String fqn = StringUtils.stripStart(((PhpClass) phpClass).getFQN(), "\\");

// find one index annotation class and stop processing on first match
final boolean[] processed = {false};
FileBasedIndex.getInstance().getFilesWithKey(AnnotationUsageIndex.KEY, new HashSet<>(Collections.singletonList(fqn)), virtualFile -> {
processed[0] = true;

// stop on first match
return false;
}, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(psiElement.getProject()), PhpFileType.INSTANCE));

// we found at least one target to provide lazy target linemarker
if(processed[0]) {
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(PhpIcons.IMPLEMENTS)
.setTargets(new CollectionNotNullLazyValue(psiElement.getProject(), fqn))
.setTooltipText("Navigate to implementations");

results.add(builder.createLineMarkerInfo(psiElement));
}
}
}

/**
* class "Foo" extends
*/
private static PsiElementPattern.Capture<PsiElement> getClassNamePattern() {
return PlatformPatterns
.psiElement(PhpTokenTypes.IDENTIFIER)
.afterLeafSkipping(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement(PhpTokenTypes.kwCLASS)
)
.withParent(PhpClass.class)
.withLanguage(PhpLanguage.INSTANCE);
}

private static class CollectionNotNullLazyValue extends NotNullLazyValue<Collection<? extends PsiElement>> {
@NotNull
private final Project project;

@NotNull
private final String fqn;

CollectionNotNullLazyValue(@NotNull Project project, @NotNull String fqn) {
this.project = project;
this.fqn = fqn;
}

@NotNull
@Override
protected Collection<? extends PsiElement> compute() {
return AnnotationUtil.getImplementationsForAnnotation(project, fqn);
}
}
}
54 changes: 54 additions & 0 deletions src/de/espend/idea/php/annotation/util/AnnotationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
Expand All @@ -14,6 +17,7 @@
import com.intellij.util.indexing.FileContent;
import com.intellij.util.indexing.ID;
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
import com.jetbrains.php.lang.PhpFileType;
import com.jetbrains.php.lang.documentation.phpdoc.PhpDocUtil;
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes;
Expand All @@ -22,6 +26,7 @@
import com.jetbrains.php.lang.parser.PhpElementTypes;
import com.jetbrains.php.lang.psi.elements.*;
import de.espend.idea.php.annotation.AnnotationStubIndex;
import de.espend.idea.php.annotation.AnnotationUsageIndex;
import de.espend.idea.php.annotation.dict.AnnotationTarget;
import de.espend.idea.php.annotation.dict.PhpAnnotation;
import de.espend.idea.php.annotation.dict.PhpDocCommentAnnotation;
Expand Down Expand Up @@ -493,6 +498,55 @@ public static StringLiteralExpression getPropertyValueAsPsiElement(@NotNull PhpD
return null;
}

@NotNull
private static Collection<PsiFile> getFilesImplementingAnnotation(@NotNull Project project, @NotNull String phpClassName) {
Collection<VirtualFile> files = new HashSet<>();

FileBasedIndex.getInstance().getFilesWithKey(AnnotationUsageIndex.KEY, new HashSet<>(Collections.singletonList(phpClassName)), virtualFile -> {
files.add(virtualFile);
return true;
}, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), PhpFileType.INSTANCE));

Collection<PsiFile> elements = new ArrayList<>();

for (VirtualFile file : files) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);

if(psiFile == null) {
continue;
}

elements.add(psiFile);
}

return elements;
}

/**
* Find all annotation usages for given class name
*
* Doctrine\ORM\Mapping\Entity => ORM\Entity(), Entity()
*
* @param project current Project
* @param fqnClassName Foobar\ClassName
* @return targets
*/
public static Collection<PhpDocTag> getImplementationsForAnnotation(@NotNull Project project, @NotNull String fqnClassName) {
Collection<PhpDocTag> psiElements = new HashSet<>();

for (PsiFile psiFile : getFilesImplementingAnnotation(project, fqnClassName)) {
psiFile.accept(new PhpDocTagAnnotationRecursiveElementWalkingVisitor(pair -> {
if(StringUtils.stripStart(pair.getFirst(), "\\").equalsIgnoreCase(StringUtils.stripStart(fqnClassName, "\\"))) {
psiElements.add(pair.getSecond());
}

return false;
}));
}

return psiElements;
}

/**
* matches "@Callback(propertyName="<value>")"
*/
Expand Down

0 comments on commit 9d0a74c

Please sign in to comment.