Skip to content

Commit

Permalink
add class constant support for import optimization and provide refere…
Browse files Browse the repository at this point in the history
  • Loading branch information
Haehnchen committed Aug 6, 2016
1 parent 02d4608 commit 5d27c04
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 5 deletions.
Expand Up @@ -3,14 +3,25 @@
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
import com.jetbrains.php.lang.psi.elements.PhpPsiElement;
import com.jetbrains.php.lang.psi.elements.PhpUse;
import de.espend.idea.php.annotation.util.AnnotationUtil;
import de.espend.idea.php.annotation.util.PhpElementsUtil;
import de.espend.idea.php.annotation.util.PluginUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
Expand All @@ -37,14 +48,52 @@ public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNu
return new PsiReference[0];
}

return new PsiReference[] {
final Collection<PsiReference> references = ContainerUtil.newSmartList(
new PhpDocTagReference((PhpDocTag) element)
};
);

// @Foo(Fo<caret>o::FooBar)
collectStaticDocClassNameElement(element, references);

return references.toArray(new PsiReference[references.size()]);
}
}

);
/**
* Collects static identifier elements: @Foo(F<caret>OO::BAR)
*/
private void collectStaticDocClassNameElement(@NotNull PsiElement element, @NotNull Collection<PsiReference> references) {
PsiElement attributes = PhpPsiUtil.getChildOfType(element, PhpDocElementTypes.phpDocAttributeList);
if (attributes == null) {
return;
}

// @Foo(Foobar::CONST) and workaround for @Foo(name{Foobar::CONST}) as this are text elements
PsiElement[] psiElements = PsiTreeUtil.collectElements(element, psiElement ->
PhpPsiUtil.isOfType(psiElement, PhpDocTokenTypes.DOC_STATIC) ||
(PhpPsiUtil.isOfType(psiElement, PhpDocTokenTypes.DOC_TEXT) && "::".equals(psiElement.getText()))
);

for (PsiElement psiElement : psiElements) {
PsiElement prevSibling = psiElement.getPrevSibling();
if(prevSibling == null) {
continue;
}

String text = prevSibling.getText();
if(StringUtils.isBlank(text)) {
continue;
}

PhpClass phpClass = PhpElementsUtil.getClassByContext(psiElement, text);
if(phpClass == null) {
continue;
}

references.add(new PhpDocIdentifierReference(psiElement.getPrevSibling(), phpClass.getFQN()));
}
}
}
);
}

private static class PhpDocTagReference extends PsiPolyVariantReferenceBase<PhpDocTag> {
Expand Down Expand Up @@ -144,4 +193,84 @@ private String getDocBlockName() {
}
}

}
/**
* Adds support for references of "@Foobar(name=Fo<caret>oBar::Const)"
*/
private static class PhpDocIdentifierReference extends PsiReferenceBase<PsiElement> {

@NotNull
private final PsiElement element;

@NotNull
private final String fqn;

PhpDocIdentifierReference(@NotNull PsiElement element, @NotNull String fqn) {
super(element);
this.element = element;
this.fqn = fqn;
}

/**
* DocTag is our reference host; need to extract text range relatively from this element
* Given: @Foobar(name=Fo<caret>oBar::Const)
*
* @return suffixed range
*/
@Override
public TextRange getRangeInElement() {
// every item is wrapped into attribute list first
PsiElement attributeList = element.getParent();
if(!(attributeList instanceof PhpPsiElement)) {
return null;
}

// offset of attribute list + docblock itself
int startOffsetInParent = element.getStartOffsetInParent() + attributeList.getStartOffsetInParent();

return new TextRange(startOffsetInParent, startOffsetInParent + element.getTextLength());
}

@Nullable
@Override
public PsiElement resolve() {
return null;
}

/**
* Attach element identify name to class of "use" usage
*
* @param psiElement PhpClass used in "use" statement
*/
@Override
public boolean isReferenceTo(PsiElement psiElement) {
if(!(psiElement instanceof PhpNamedElement)) {
return false;
}

String text = getElement().getText();
if(StringUtils.isBlank(text)) {
return false;
}

PsiElement namespace = element.getPrevSibling();
if(PhpPsiUtil.isOfType(namespace, PhpDocTokenTypes.DOC_NAMESPACE)) {
// @TODO: namespace not supported
return false;
}

String classByContext = PhpElementsUtil.getFqnForClassNameByContext(element, text);
if(classByContext != null) {
return StringUtils.stripStart(((PhpClass) psiElement).getFQN(), "\\")
.equalsIgnoreCase(StringUtils.stripStart(fqn, "\\"));
}

return false;
}

@NotNull
@Override
public Object[] getVariants() {
return new Object[0];
}
}
}
15 changes: 15 additions & 0 deletions src/de/espend/idea/php/annotation/util/PhpElementsUtil.java
Expand Up @@ -170,6 +170,21 @@ public static PhpClass getClassByContext(PsiElement psiElement, String className
return PhpElementsUtil.getClass(psiElement.getProject(), className);
}

/**
* Resolve clasname with scoped namespace imports on inside PhpDocTag
*
* @param psiElement PhpDocTag scoped element
* @param className with namespace
*/
@Nullable
public static String getFqnForClassNameByContext(@NotNull PsiElement psiElement, @NotNull String className) {
PhpDocTag phpDocTag = PsiTreeUtil.getParentOfType(psiElement, PhpDocTag.class);
if(phpDocTag == null) {
return null;
}

return AnnotationUtil.getUseImportMap(phpDocTag).get(className);
}

@Nullable
public static String getStringValue(@Nullable PsiElement psiElement) {
Expand Down
Expand Up @@ -110,6 +110,58 @@ public void testThatOptimizeImportShouldSupportAnnotationNamespaceOnlyByZend() {
assertTrue(optimized.contains("use Zend\\Form\\Annotation;"));
}

public void testThatOptimizeImportShouldSupportStringConstants() {
String[] strings = {
"@Car(Foo::MY_CONST)",
"@Car(name=Foo::MY_CONST)",
"@Car(name={@Car(Foo::MY_CONST)})",
};

for (String string : strings) {
String optimized = optimizeImports("<?php\n" +
"\n" +
"namespace My;\n" +
"\n" +
"use FooBar\\Car;\n" +
"use MyConstant\\Foo;\n" +
"\n" +
"class Foo\n" +
"{\n" +
" /**\n" +
" * " + string +
" */\n" +
" public function foo()\n" +
" {\n" +
" }\n" +
"}\n"
);

assertTrue(optimized.contains("use MyConstant\\Foo;"));
}
}

public void testThatOptimizeImportShouldRemoveNamespaceWithoutUse() {
String optimized = optimizeImports("<?php\n" +
"\n" +
"namespace My;\n" +
"\n" +
"use FooBar\\Car;\n" +
"use MyConstant\\Foo;\n" +
"\n" +
"class Foo\n" +
"{\n" +
" /**\n" +
" * @Car(Foo\\Foo::MY_CONST)" +
" */\n" +
" public function foo()\n" +
" {\n" +
" }\n" +
"}\n"
);

assertFalse(optimized.contains("use MyConstant\\Foo;"));
}

@NotNull
private String optimizeImports(@NotNull String content) {
PsiFile psiFile = myFixture.configureByText(PhpFileType.INSTANCE, content);
Expand Down
Expand Up @@ -25,5 +25,12 @@ class Exclude
}
}

namespace MyConstant
{
class Foo
{
const MY_CONST = 'foobar';
}
}


0 comments on commit 5d27c04

Please sign in to comment.