diff --git a/src/cz/juzna/intellij/nette/completion/ComponentCompletionContributor.java b/src/cz/juzna/intellij/nette/completion/ComponentCompletionContributor.java index ef3d103..615906a 100644 --- a/src/cz/juzna/intellij/nette/completion/ComponentCompletionContributor.java +++ b/src/cz/juzna/intellij/nette/completion/ComponentCompletionContributor.java @@ -9,9 +9,10 @@ import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import com.jetbrains.php.lang.psi.resolve.types.PhpType; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import cz.juzna.intellij.nette.utils.ComponentUtil; import cz.juzna.intellij.nette.utils.PhpIndexUtil; -import cz.juzna.intellij.nette.utils.StringUtil; +import cz.juzna.intellij.nette.utils.PsiUtil; import org.jetbrains.annotations.NotNull; public class ComponentCompletionContributor extends CompletionContributor { @@ -24,13 +25,24 @@ private class ComponentNameCompletionProvider extends CompletionProvider getChildrenBase() { Collection children = new ArrayList(); if (expandClass && getElement() instanceof PhpClass) { - for (Method method : ComponentUtil.getFactoryMethods((PhpClass) getElement(), null)) { + ComponentSearcher.ComponentQuery query = new ComponentSearcher.ComponentQuery((PhpClass) getElement()); + for (Method method : ComponentSearcher.findMethods(query)) { children.add(new ComponentTreeElement(method)); } } else if (getElement() instanceof Method) { Method method = (Method) getElement(); for (PhpClass cls : PhpIndexUtil.getClasses(method, method.getProject())) { children.add(new ComponentTreeElement(cls, false)); - for (Method m : ComponentUtil.getFactoryMethods(cls, null)) { + ComponentSearcher.ComponentQuery query = new ComponentSearcher.ComponentQuery(cls); + for (Method m : ComponentSearcher.findMethods(query)) { children.add(new ComponentTreeElement(m)); } } diff --git a/src/cz/juzna/intellij/nette/inspections/CreateComponentReturnFormTypeInspection.java b/src/cz/juzna/intellij/nette/inspections/CreateComponentReturnFormTypeInspection.java index adb129b..b32aa16 100644 --- a/src/cz/juzna/intellij/nette/inspections/CreateComponentReturnFormTypeInspection.java +++ b/src/cz/juzna/intellij/nette/inspections/CreateComponentReturnFormTypeInspection.java @@ -15,7 +15,7 @@ import com.jetbrains.php.lang.psi.elements.PhpReturn; import com.jetbrains.php.lang.psi.elements.PhpTypedElement; import com.jetbrains.php.lang.psi.resolve.types.PhpType; -import cz.juzna.intellij.nette.utils.ComponentUtil; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -49,7 +49,10 @@ public ProblemDescriptor[] checkFile(PsiFile file, InspectionManager manager, bo Collection classes = PhpPsiUtil.findAllClasses((PhpFile) file); Collection invalidReturnForms = new ArrayList(); for (PhpClass cls : classes) { - for (final Method method : ComponentUtil.getFactoryMethods(cls, null, true)) { + + ComponentSearcher.ComponentQuery query = new ComponentSearcher.ComponentQuery(cls); + query.filterOwn(); + for (final Method method : ComponentSearcher.findMethods(query)) { InvalidCreateComponentMethodVisitor visitor = new InvalidCreateComponentMethodVisitor(); method.accept(visitor); if (visitor.isInvalid()) { diff --git a/src/cz/juzna/intellij/nette/reference/ComponentReference.java b/src/cz/juzna/intellij/nette/reference/ComponentReference.java index f6ce777..f8945b1 100644 --- a/src/cz/juzna/intellij/nette/reference/ComponentReference.java +++ b/src/cz/juzna/intellij/nette/reference/ComponentReference.java @@ -1,11 +1,12 @@ package cz.juzna.intellij.nette.reference; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReferenceBase; import com.intellij.psi.ResolveResult; import com.intellij.util.IncorrectOperationException; -import com.jetbrains.php.lang.psi.elements.Method; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import cz.juzna.intellij.nette.utils.ComponentUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,24 +16,33 @@ public class ComponentReference extends PsiReferenceBase.Poly { - public ComponentReference(@NotNull PsiElement element) { - super(element); + private final String path; + + public ComponentReference(@NotNull PsiElement element, String path) { + super(element, true); + this.path = path; } @NotNull @Override public ResolveResult[] multiResolve(boolean b) { - if (getElement().getParent().getParent() == null) { + PsiElement el = getElement().getParent().getParent(); + if (el == null) { return new ResolveResult[0]; } - Method[] factoryMethods = ComponentUtil.getFactoryMethods(getElement().getParent().getParent(), true); - Collection results = new ArrayList(factoryMethods.length); - for (final Method method : factoryMethods) { + ComponentSearcher.ComponentQuery query = ComponentSearcher.createQuery(el); + query.withPath(); + Collection searchResults = ComponentSearcher.find(query); + Collection results = new ArrayList(searchResults.size()); + for (final ComponentSearcher.ComponentSearchResult searchResult : searchResults) { + if (!searchResult.getPath().equals(path)) { + continue; + } results.add(new ResolveResult() { @Nullable @Override public PsiElement getElement() { - return method; + return searchResult.getMethod(); } @Override @@ -46,6 +56,11 @@ public boolean isValidResult() { return results.toArray(result); } + @Override + public TextRange getRangeInElement() { + return new TextRange(path.contains("-") ? path.lastIndexOf("-") + 2 : 1, path.length() + 1); + } + @NotNull @Override public Object[] getVariants() { @@ -56,7 +71,13 @@ public Object[] getVariants() { public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { String componentName = ComponentUtil.methodToComponentName(newElementName); if (getElement() instanceof StringLiteralExpression && componentName != null) { - ((StringLiteralExpression) getElement()).updateText(componentName); + StringLiteralExpression stringLiteral = (StringLiteralExpression) getElement(); + TextRange range = getRangeInElement(); + String name = stringLiteral.getContents(); + name = (range.getStartOffset() > 1 ? name.substring(0, range.getStartOffset() - 1) : "") + + componentName + + name.substring(range.getEndOffset() - 1); + stringLiteral.updateText(name); return getElement(); } diff --git a/src/cz/juzna/intellij/nette/reference/ComponentReferenceContributor.java b/src/cz/juzna/intellij/nette/reference/ComponentReferenceContributor.java index a3bba0a..fad28a8 100644 --- a/src/cz/juzna/intellij/nette/reference/ComponentReferenceContributor.java +++ b/src/cz/juzna/intellij/nette/reference/ComponentReferenceContributor.java @@ -4,10 +4,12 @@ import com.intellij.psi.*; import com.intellij.util.ProcessingContext; import com.jetbrains.php.lang.parser.PhpElementTypes; -import com.jetbrains.php.lang.psi.elements.Method; -import cz.juzna.intellij.nette.utils.ComponentUtil; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; + public class ComponentReferenceContributor extends PsiReferenceContributor { @Override @@ -19,11 +21,19 @@ public PsiReference[] getReferencesByElement(PsiElement psiElement, ProcessingCo if (psiElement.getParent() == null || psiElement.getParent().getParent() == null) { return new PsiReference[0]; } - Method[] factoryMethods = ComponentUtil.getFactoryMethods(psiElement.getParent().getParent(), true); - if (factoryMethods.length == 0) { + PsiElement el = psiElement.getParent().getParent(); + ComponentSearcher.ComponentQuery query = ComponentSearcher.createQuery(el); + query.withPath(); + Collection result = ComponentSearcher.find(query); + if (result.size() == 0) { return new PsiReference[0]; } - return new PsiReference[]{new ComponentReference(psiElement)}; + Collection refs = new ArrayList(result.size()); + for (ComponentSearcher.ComponentSearchResult searchResult : result) { + refs.add(new ComponentReference(psiElement, searchResult.getPath())); + } + + return refs.toArray(new PsiReference[refs.size()]); } }); } diff --git a/src/cz/juzna/intellij/nette/reference/ComponentReferenceSearch.java b/src/cz/juzna/intellij/nette/reference/ComponentReferenceSearch.java index a6e428c..7ee3b46 100644 --- a/src/cz/juzna/intellij/nette/reference/ComponentReferenceSearch.java +++ b/src/cz/juzna/intellij/nette/reference/ComponentReferenceSearch.java @@ -10,10 +10,9 @@ import com.intellij.util.Processor; import com.jetbrains.php.lang.psi.elements.Method; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import cz.juzna.intellij.nette.utils.ComponentUtil; -import java.util.Arrays; - public class ComponentReferenceSearch extends QueryExecutorBase { @@ -36,9 +35,13 @@ public boolean execute(PsiElement psiElement, int i) { return true; } PsiElement el = psiElement.getParent().getParent(); - Method[] methods = ComponentUtil.getFactoryMethods(el); - if (Arrays.asList(methods).contains(method)) { - processor.process(new ComponentReference(psiElement)); + ComponentSearcher.ComponentQuery query = ComponentSearcher.createQuery(el); + query.withPath(); + for (ComponentSearcher.ComponentSearchResult result : ComponentSearcher.find(query)) { + if (result.getMethod() == method) { + processor.process(new ComponentReference(psiElement, result.getPath())); + } + } return true; diff --git a/src/cz/juzna/intellij/nette/typeProvider/ComponentTypeProvider.java b/src/cz/juzna/intellij/nette/typeProvider/ComponentTypeProvider.java index 4a435c0..0037217 100644 --- a/src/cz/juzna/intellij/nette/typeProvider/ComponentTypeProvider.java +++ b/src/cz/juzna/intellij/nette/typeProvider/ComponentTypeProvider.java @@ -12,13 +12,12 @@ import com.jetbrains.php.lang.psi.elements.PhpTypedElement; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider2; -import cz.juzna.intellij.nette.utils.ComponentUtil; +import cz.juzna.intellij.nette.utils.ComponentSearcher; import cz.juzna.intellij.nette.utils.ElementValueResolver; import cz.juzna.intellij.nette.utils.PhpIndexUtil; import cz.juzna.intellij.nette.utils.PhpPsiUtil; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -77,14 +76,8 @@ public Collection getBySignature(String s, Project pr } String componentName = parts[0]; Collection classes = PhpIndexUtil.getByType(parts[1].split(TYPE_SEPARATOR), PhpIndex.getInstance(project)); - Collection result = new ArrayList(); - for (PhpClass cls : classes) { - if (!ComponentUtil.isContainer(cls)) { - continue; - } - result.addAll(ComponentUtil.getFactoryMethodsByName(cls, componentName, false)); - } - return result; + return ComponentSearcher.findMethods(new ComponentSearcher.ComponentQuery(componentName, classes)); + } } diff --git a/src/cz/juzna/intellij/nette/utils/ComponentSearcher.java b/src/cz/juzna/intellij/nette/utils/ComponentSearcher.java new file mode 100644 index 0000000..ea4739b --- /dev/null +++ b/src/cz/juzna/intellij/nette/utils/ComponentSearcher.java @@ -0,0 +1,234 @@ +package cz.juzna.intellij.nette.utils; + +import com.intellij.psi.PsiElement; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.psi.elements.ArrayAccessExpression; +import com.jetbrains.php.lang.psi.elements.ArrayIndex; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.PhpTypedElement; +import com.jetbrains.php.lang.psi.resolve.types.PhpType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + + +public class ComponentSearcher { + + public enum Match {EXACT, PREFIX} + + private static PhpType container = new PhpType().add("Nette\\ComponentModel\\Container"); + public static String factoryMethodPrefix = "createComponent"; + + public static Collection find(ComponentQuery query) { + return findInternal(query, ""); + } + + public static Collection findMethods(ComponentQuery query) { + Collection result = find(query); + Collection methods = new ArrayList(result.size()); + for (ComponentSearchResult searchResult : result) { + methods.add(searchResult.getMethod()); + } + return methods; + } + + private static Collection findInternal(ComponentQuery query, String prefix) { + if (query.getClasses().isEmpty()) { + return Collections.emptyList(); + } + if (query.hasSubQuery() || query.getMatchMode() == Match.EXACT) { + return getByName(query, prefix); + } + + return getByPrefix(query, prefix); + } + + private static Collection getByName(ComponentQuery query, String prefix) { + Collection result1 = new ArrayList(); + + String methodName = factoryMethodPrefix + StringUtil.upperFirst(query.getPartName()); + String path = prefix + (prefix.isEmpty() ? "" : "-") + query.getPartName(); + for (PhpClass cls : query.getClasses()) { + if (!isContainer(cls)) { + continue; + } + Method m = query.isOnlyOwn() ? cls.findOwnMethodByName(methodName) : cls.findMethodByName(methodName); + if (m != null) { + result1.add(new ComponentSearchResult(path, m)); + } + } + if (!query.hasSubQuery()) { + return result1; + } + Collection result2 = new ArrayList(); + if (query.isWithPath()) { + result2.addAll(result1); + } + for (ComponentSearchResult searchResult : result1) { + Method method = searchResult.getMethod(); + Collection classes = PhpIndexUtil.getClasses(method, method.getProject()); + result2.addAll(findInternal(query.getSubQuery(classes), path)); + } + return result2; + } + + private static Collection getByPrefix(ComponentQuery query, String prefix) { + Collection result = new ArrayList(); + String path = prefix + (prefix.isEmpty() ? "" : "-") + query.getPartName(); + for (PhpClass cls : query.getClasses()) { + if (!isContainer(cls)) { + continue; + } + for (Method method : query.isOnlyOwn() ? Arrays.asList(cls.getOwnMethods()) : cls.getMethods()) { + if (method.getName().startsWith(factoryMethodPrefix + StringUtil.upperFirst(query.getPartName())) && !method.getName().equals(factoryMethodPrefix)) { + result.add(new ComponentSearchResult(path, method)); + } + } + + } + return result; + } + + + public static ComponentQuery createQuery(PsiElement el) { + String componentName; + Collection classes; + if (el instanceof ArrayAccessExpression) { + ArrayIndex index = ((ArrayAccessExpression) el).getIndex(); + if (index == null || !(el.getFirstChild() instanceof PhpTypedElement)) { + return new ComponentQuery("", Collections.emptyList()); + } + componentName = ElementValueResolver.resolve(index.getValue()); + classes = PhpIndexUtil.getClasses((PhpTypedElement) el.getFirstChild(), el.getProject()); + } else if (el instanceof MethodReference) { + MethodReference methodRef = (MethodReference) el; + if (methodRef.getClassReference() == null + || methodRef.getName() == null + || !methodRef.getName().equals("getComponent") + || methodRef.getParameters().length != 1) { + return new ComponentQuery("", Collections.emptyList()); + } + componentName = ElementValueResolver.resolve(methodRef.getParameters()[0]); + classes = PhpIndexUtil.getClasses(methodRef.getClassReference(), methodRef.getProject()); + } else { + return new ComponentQuery("", Collections.emptyList()); + } + + + return new ComponentQuery(componentName, classes); + } + + private static boolean isContainer(PhpClass csl) { + return container.isConvertibleFrom(csl.getType(), PhpIndex.getInstance(csl.getProject())); + } + + public static class ComponentQuery { + + private final Collection classes; + + private String remainingName; + + private boolean onlyOwn = false; + + private Match matchMode = Match.EXACT; + + private boolean path = false; + + public ComponentQuery(PhpClass cls) { + this("", Collections.singletonList(cls)); + this.match(Match.PREFIX); + } + + public ComponentQuery(String name, Collection classes) { + this.remainingName = name; + this.classes = classes; + } + + public ComponentQuery filterOwn() { + this.onlyOwn = true; + + return this; + } + + public ComponentQuery match(Match matchMode) { + this.matchMode = matchMode; + + return this; + } + + public ComponentQuery withPath() { + this.path = true; + + return this; + } + + public String getPartName() { + int pos = this.remainingName.indexOf("-"); + + return pos == -1 ? this.remainingName : this.remainingName.substring(0, pos); + } + + public boolean isOnlyOwn() { + return onlyOwn; + } + + public Match getMatchMode() { + return matchMode; + } + + public boolean hasSubQuery() { + return this.remainingName.contains("-"); + } + + public ComponentQuery getSubQuery(Collection classes) { + int pos = this.remainingName.indexOf("-"); + if (pos == -1) { + return null; + } + + ComponentQuery query = new ComponentQuery(remainingName.substring(pos + 1), classes); + if (this.path) { + query.withPath(); + } + if (this.onlyOwn) { + query.filterOwn(); + } + query.match(this.matchMode); + + return query; + } + + public boolean isWithPath() { + return path; + } + + public Collection getClasses() { + return classes; + } + } + + public static class ComponentSearchResult { + + private final String path; + private final Method method; + + public ComponentSearchResult(String path, Method method) { + + this.path = path; + this.method = method; + } + + public Method getMethod() { + return method; + } + + public String getPath() { + return path; + } + } + +} diff --git a/src/cz/juzna/intellij/nette/utils/ComponentUtil.java b/src/cz/juzna/intellij/nette/utils/ComponentUtil.java index 4813db3..4c069b9 100644 --- a/src/cz/juzna/intellij/nette/utils/ComponentUtil.java +++ b/src/cz/juzna/intellij/nette/utils/ComponentUtil.java @@ -1,109 +1,11 @@ package cz.juzna.intellij.nette.utils; - -import com.intellij.psi.PsiElement; -import com.jetbrains.php.PhpIndex; -import com.jetbrains.php.lang.psi.elements.*; -import com.jetbrains.php.lang.psi.resolve.types.PhpType; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - public class ComponentUtil { - private static PhpType container = new PhpType().add("Nette\\ComponentModel\\Container"); public static String factoryMethodPrefix = "createComponent"; - public static boolean isContainer(PhpClass csl) { - return container.isConvertibleFrom(csl.getType(), PhpIndex.getInstance(csl.getProject())); - } - - @NotNull - public static Method[] getFactoryMethods(PsiElement el) { - return getFactoryMethods(el, false); - } - - @NotNull - public static Method[] getFactoryMethods(PsiElement el, boolean onlyWithName) { - String componentName = null; - Collection classes = null; - if (el instanceof ArrayAccessExpression) { - ArrayIndex index = ((ArrayAccessExpression) el).getIndex(); - if (index == null || !(el.getFirstChild() instanceof PhpTypedElement)) { - return new Method[0]; - } - componentName = ElementValueResolver.resolve(index.getValue()); - classes = PhpIndexUtil.getClasses((PhpTypedElement) el.getFirstChild(), el.getProject()); - } else if (el instanceof MethodReference) { - MethodReference methodRef = (MethodReference) el; - if (methodRef.getClassReference() == null - || methodRef.getName() == null - || !methodRef.getName().equals("getComponent") - || methodRef.getParameters().length != 1) { - return new Method[0]; - } - componentName = ElementValueResolver.resolve(methodRef.getParameters()[0]); - classes = PhpIndexUtil.getClasses(methodRef.getClassReference(), methodRef.getProject()); - } - if (classes == null || classes.isEmpty() || (componentName == null && onlyWithName)) { - return new Method[0]; - } - Collection methods = new ArrayList(); - for (PhpClass currentClass : classes) { - if (!isContainer(currentClass)) { - continue; - } - methods.addAll(getFactoryMethodsByName(currentClass, onlyWithName ? componentName : null, false)); - } - Method[] result = new Method[methods.size()]; - - return methods.toArray(result); - } - - @NotNull - public static Collection getFactoryMethods(@NotNull PhpClass cls, String componentName) { - return getFactoryMethods(cls, componentName, false); - } - - @NotNull - public static Collection getFactoryMethods(@NotNull PhpClass cls, String componentName, boolean onlyOwn) { - if (!isContainer(cls)) { - return Collections.emptyList(); - } - - return getFactoryMethodsByName(cls, componentName, onlyOwn); - } - - public static Collection getFactoryMethodsByName(@NotNull PhpClass cls, String componentName, boolean onlyOwn) { - Collection methods = new ArrayList(); - if (componentName != null) { - String method = factoryMethodPrefix + StringUtil.upperFirst(componentName); - Method m = cls.findMethodByName(method); - if (m != null) { - methods.add(m); - } - } else { - Method[] classMethods; - if (!onlyOwn) { - Collection tmpMethods = cls.getMethods(); - classMethods = new Method[tmpMethods.size()]; - tmpMethods.toArray(classMethods); - } else { - classMethods = cls.getOwnMethods(); - } - for (Method method : classMethods) { - if (method.getName().startsWith(factoryMethodPrefix) && method.getName().length() > factoryMethodPrefix.length()) { - methods.add(method); - } - } - } - return methods; - } - @Nullable public static String methodToComponentName(String methodName) { if (!methodName.startsWith(factoryMethodPrefix) || methodName.length() <= factoryMethodPrefix.length()) { diff --git a/src/cz/juzna/intellij/nette/utils/PsiUtil.java b/src/cz/juzna/intellij/nette/utils/PsiUtil.java new file mode 100644 index 0000000..f4f7530 --- /dev/null +++ b/src/cz/juzna/intellij/nette/utils/PsiUtil.java @@ -0,0 +1,17 @@ +package cz.juzna.intellij.nette.utils; + + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; + +public class PsiUtil { + + @Nullable + public static PsiElement getParentAtLevel(PsiElement element, int level) { + for (; element != null && level > 0; level--) { + element = element.getParent(); + } + return element; + } + +}