diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 18ee856c..502571c1 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -1,34 +1,48 @@ com.yii2support Yii2 Support - 0.3.10.11 + 0.3.17.0 NVlad -
- Features +

Yii2 Support for PhpStorm

+ +

Views

- ]]>
- i18n + +

Configuration arrays

+

Code completion for Yii configuration arrays. Works both in configuration files and on object instantiation.
+ Following cases are supported:

+ +

Go To Declaration, Rename, Find usages and Help popup works whenever code completion works.

+ ]]> + + +
  • Code completion for object configuration array (with "class" key), config files, widgets and object creating
  • + ]]>
    @@ -45,16 +59,11 @@ - - - - + + - + + + + + - - - diff --git a/resources/inspectionDescriptions/MissedFieldInspection.html b/resources/inspectionDescriptions/MissedFieldInspection.html new file mode 100644 index 00000000..82e6e70d --- /dev/null +++ b/resources/inspectionDescriptions/MissedFieldInspection.html @@ -0,0 +1,5 @@ + + +Missed field in object. + + \ No newline at end of file diff --git a/src/com/nvlad/yii2support/components/ComponentConfigCompletionContributor.java b/src/com/nvlad/yii2support/components/ComponentConfigCompletionContributor.java deleted file mode 100644 index 456378c8..00000000 --- a/src/com/nvlad/yii2support/components/ComponentConfigCompletionContributor.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.nvlad.yii2support.components; - -import com.intellij.codeInsight.completion.CompletionContributor; -import com.intellij.codeInsight.completion.CompletionType; -import com.intellij.patterns.ElementPattern; -import com.intellij.patterns.PlatformPatterns; -import com.intellij.psi.PsiElement; -import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression; -import com.jetbrains.php.lang.psi.elements.NewExpression; -import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; -import com.nvlad.yii2support.common.Patterns; -import org.jetbrains.annotations.NotNull; - -/** - * Created by NVlad on 11.01.2017. - */ -public class ComponentConfigCompletionContributor extends CompletionContributor { - public ComponentConfigCompletionContributor() { - extend(CompletionType.BASIC, ElementPattern(), new ComponentConfigCompletionProvider()); - } - - @Override - public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) { - if ((typeChar == '\'' || typeChar == '"') && position.getParent() instanceof ArrayCreationExpression) { - return true; - } - - return false; - } - - private static ElementPattern ElementPattern() { - //noinspection unchecked - return PlatformPatterns.psiElement() - .withParent(PlatformPatterns.psiElement(StringLiteralExpression.class) - .withParent(PlatformPatterns.or( - PlatformPatterns.psiElement().withSuperParent(3, NewExpression.class), - Patterns.withHashKey() - .withParent(PlatformPatterns.psiElement().withSuperParent(3, NewExpression.class)) - ))); - } -} diff --git a/src/com/nvlad/yii2support/components/ComponentConfigCompletionProvider.java b/src/com/nvlad/yii2support/components/ComponentConfigCompletionProvider.java deleted file mode 100644 index b4b2ad04..00000000 --- a/src/com/nvlad/yii2support/components/ComponentConfigCompletionProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.nvlad.yii2support.components; - -import com.intellij.codeInsight.completion.CompletionParameters; -import com.intellij.codeInsight.completion.CompletionProvider; -import com.intellij.codeInsight.completion.CompletionResultSet; -import com.intellij.util.ProcessingContext; -import com.jetbrains.php.lang.psi.elements.*; -import org.jetbrains.annotations.NotNull; - -/** - * Created by NVlad on 11.01.2017. - */ -public class ComponentConfigCompletionProvider extends CompletionProvider { - @Override - protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) { - PhpExpression element = (PhpExpression) completionParameters.getPosition().getParent(); - - NewExpression newExpression = ComponentUtil.configForNewExpression(element); - if (newExpression != null) { - PhpClass phpClass = ComponentUtil.getPhpClass(newExpression); - if (phpClass != null) { - Method constructor = phpClass.getConstructor(); - ParameterList parameterList = newExpression.getParameterList(); - if (constructor != null && parameterList != null) { - int paramIndex = ComponentUtil.paramIndexForElement(element); - - if (paramIndex != -1) { - Parameter[] parameters = constructor.getParameters(); - - if (paramIndex < parameters.length && parameters[paramIndex].getName().equals("config")) { - for (Field field : ComponentUtil.getClassFields(phpClass)) { - completionResultSet.addElement(new ComponentFieldLookupElement(element, field)); - } - } - } - } - } - } - } -} diff --git a/src/com/nvlad/yii2support/components/ComponentFieldLookupElement.java b/src/com/nvlad/yii2support/components/ComponentFieldLookupElement.java deleted file mode 100644 index 5bdc344d..00000000 --- a/src/com/nvlad/yii2support/components/ComponentFieldLookupElement.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.nvlad.yii2support.components; - -import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementPresentation; -import com.intellij.openapi.editor.Document; -import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; -import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag; -import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression; -import com.jetbrains.php.lang.psi.elements.ConstantReference; -import com.jetbrains.php.lang.psi.elements.Field; -import com.jetbrains.php.lang.psi.elements.PhpExpression; -import org.jetbrains.annotations.NotNull; - -/** - * Created by NVlad on 11.01.2017. - */ -public class ComponentFieldLookupElement extends LookupElement { - private PhpExpression myElement; - private Field myField; - - ComponentFieldLookupElement(PhpExpression element, Field field) { - myElement = element; - myField = field; - } - - @NotNull - @Override - public String getLookupString() { - if (myElement instanceof ConstantReference) { - return "'" + myField.getName() + "'"; - } - - return myField.getName(); - } - - @Override - public void renderElement(LookupElementPresentation presentation) { - presentation.setIcon(myField.getIcon()); - presentation.setItemText(myField.getName()); - presentation.setItemTextBold(true); - - presentation.setTypeText(myField.getType().toString()); - presentation.setTypeGrayed(true); - - PhpDocComment docComment = myField.getDocComment(); - if (docComment != null) { - PhpDocParamTag paramTag = docComment.getVarTag(); - if (paramTag != null) { - presentation.setTailText(" " + paramTag.getTagValue(), true); - } - } - - } - - @Override - public void handleInsert(InsertionContext context) { - super.handleInsert(context); - - Document document = context.getDocument(); - int insertPosition = context.getSelectionEndOffset(); - - if (myElement.getParent().getParent() instanceof ArrayCreationExpression) { - document.insertString(insertPosition + 1, " => "); - insertPosition += 5; - - context.getEditor().getCaretModel().getCurrentCaret().moveToOffset(insertPosition); - } - } -} diff --git a/src/com/nvlad/yii2support/components/ComponentUtil.java b/src/com/nvlad/yii2support/components/ComponentUtil.java deleted file mode 100644 index fcb4c554..00000000 --- a/src/com/nvlad/yii2support/components/ComponentUtil.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.nvlad.yii2support.components; - -import com.intellij.psi.PsiElement; -import com.intellij.util.ArrayUtil; -import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocProperty; -import com.jetbrains.php.lang.psi.elements.*; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashSet; - -/** - * Created by NVlad on 11.01.2017. - */ -class ComponentUtil { - @Nullable - static NewExpression configForNewExpression(PsiElement psiElement) { - if (psiElement instanceof NewExpression) { - return (NewExpression) psiElement; - } - - PsiElement parent = psiElement.getParent(); - if (parent != null) { - return configForNewExpression(parent); - } - - return null; - } - - static int paramIndexForElement(PsiElement psiElement) { - PsiElement parent = psiElement.getParent(); - if (parent == null) { - return -1; - } - - if (parent instanceof ParameterList) { - return ArrayUtil.indexOf(((ParameterList) parent).getParameters(), psiElement); - } - - return paramIndexForElement(parent); - } - - - @Nullable - static PhpClass getPhpClass(PhpPsiElement phpPsiElement) { - while (phpPsiElement != null) { - if (phpPsiElement instanceof ClassReference) { - return (PhpClass) ((ClassReference) phpPsiElement).resolve(); - } - if (phpPsiElement instanceof NewExpression) { - ClassReference classReference = ((NewExpression) phpPsiElement).getClassReference(); - if (classReference != null) { - PhpPsiElement resolve = (PhpPsiElement) classReference.resolve(); - if (resolve instanceof PhpClass) { - return (PhpClass) resolve; - } - } - } - - phpPsiElement = (PhpPsiElement) phpPsiElement.getParent(); - } - - return null; - } - - static Collection getClassFields(PhpClass phpClass) { - final HashSet result = new HashSet<>(); - - final Collection fields = phpClass.getFields(); - final Collection methods = phpClass.getMethods(); - for (Field field : fields) { - if (field.isConstant()) { - continue; - } - - final PhpModifier modifier = field.getModifier(); - if (!modifier.isPublic() || modifier.isStatic()) { - continue; - } - - if (field instanceof PhpDocProperty) { - final String setter = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); - Boolean setterExist = false; - for (Method method : methods) { - if (method.getName().equals(setter)) { - setterExist = true; - break; - } - } - if (!setterExist) { - String getter = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); - Boolean getterExist = false; - for (Method method : methods) { - if (method.getName().equals(getter)) { - getterExist = true; - break; - } - } - if (getterExist) { - continue; - } - } - } - - result.add(field); - } - - return result; - } -} diff --git a/src/com/nvlad/yii2support/objectfactory/ClassUtils.java b/src/com/nvlad/yii2support/objectfactory/ClassUtils.java index 418523a8..269549ce 100644 --- a/src/com/nvlad/yii2support/objectfactory/ClassUtils.java +++ b/src/com/nvlad/yii2support/objectfactory/ClassUtils.java @@ -1,14 +1,14 @@ package com.nvlad.yii2support.objectfactory; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocProperty; import com.jetbrains.php.lang.psi.elements.*; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; /** @@ -29,7 +29,7 @@ public static PhpClass getPhpClassUniversal(Project project, PhpPsiElement value return getPhpClass(classRef); } if (value instanceof StringLiteralExpression) { - StringLiteralExpression str = (StringLiteralExpression)value; + StringLiteralExpression str = (StringLiteralExpression) value; PhpIndex phpIndex = PhpIndex.getInstance(project); PhpClass classRef = getClass(phpIndex, str.getContents()); return classRef; @@ -39,11 +39,11 @@ public static PhpClass getPhpClassUniversal(Project project, PhpPsiElement value @Nullable static public PhpClass getClass(PhpIndex phpIndex, String className) { - Collection classes = phpIndex.getClassesByFQN(className); + Collection classes = phpIndex.getAnyByFQN(className); return classes.isEmpty() ? null : classes.iterator().next(); } - @Nullable + @Nullable static PhpClass getPhpClass(PhpPsiElement phpPsiElement) { while (phpPsiElement != null) { if (phpPsiElement instanceof ClassConstantReference) { @@ -68,14 +68,14 @@ static PhpClass getPhpClass(PhpPsiElement phpPsiElement) { return null; } - static boolean isClassInherits(PhpClass classObject, PhpClass superClass) { + static boolean isClassInheritsOrEqual(PhpClass classObject, PhpClass superClass) { if (classObject == null || superClass == null) return false; - if ( classObject.getSuperClass() != null) { - if (classObject.getSuperClass().isEquivalentTo(superClass)) - return true; - else - return isClassInherits(classObject.getSuperClass(), superClass); + if (classObject != null) { + if (classObject.isEquivalentTo(superClass)) + return true; + else + return isClassInheritsOrEqual(classObject.getSuperClass(), superClass); } return false; } @@ -94,8 +94,8 @@ static Collection getClassSetMethods(PhpClass phpClass) { for (Method method : methods) { String methodName = method.getName(); - int pCount = method.getParameters().length; - if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && + int pCount = method.getParameters().length; + if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && Character.isUpperCase(methodName.charAt(3))) { result.add(method); } @@ -111,61 +111,70 @@ static String removeQuotes(String str) { static PhpClassMember findField(PhpClass phpClass, String fieldName) { + if (phpClass == null || fieldName == null) + return null; fieldName = ClassUtils.removeQuotes(fieldName); + final Collection fields = phpClass.getFields(); final Collection methods = phpClass.getMethods(); - for (Field field : fields) { - if (! field.getName().equals(fieldName)) - continue; + if (fields != null) { + for (Field field : fields) { + if (!field.getName().equals(fieldName)) + continue; - if (field.isConstant()) { - continue; - } - - final PhpModifier modifier = field.getModifier(); - if (!modifier.isPublic() || modifier.isStatic()) { - continue; - } + if (field.isConstant()) { + continue; + } - if (field instanceof PhpDocProperty) { - final String setter = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); - Boolean setterExist = false; - for (Method method : methods) { - if (method.getName().equals(setter)) { - setterExist = true; - break; - } + final PhpModifier modifier = field.getModifier(); + if (!modifier.isPublic() || modifier.isStatic()) { + continue; } - if (!setterExist) { - String getter = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); - Boolean getterExist = false; + + + if (field instanceof PhpDocProperty) { + final String setter = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); + Boolean setterExist = false; for (Method method : methods) { - if (method.getName().equals(getter)) { - getterExist = true; + if (method.getName().equals(setter)) { + setterExist = true; break; } } - if (getterExist) { - continue; + if (!setterExist) { + String getter = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); + Boolean getterExist = false; + for (Method method : methods) { + if (method.getName().equals(getter)) { + getterExist = true; + break; + } + } + if (getterExist) { + continue; + } } + } + return field; } - - return field; } - for (Method method : methods) { - String methodName = method.getName(); - int pCount = method.getParameters().length; - if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && - Character.isUpperCase(methodName.charAt(3))) { - String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); - if (propertyName.equals(fieldName)) - return method; + if (methods != null) { + for (Method method : methods) { + String methodName = method.getName(); + int pCount = method.getParameters().length; + if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && + Character.isUpperCase(methodName.charAt(3))) { + String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); + if (propertyName.equals(fieldName)) + return method; + + } } } @@ -173,7 +182,22 @@ static PhpClassMember findField(PhpClass phpClass, String fieldName) { return null; } + static int paramIndexForElement(PsiElement psiElement) { + PsiElement parent = psiElement.getParent(); + if (parent == null) { + return -1; + } + + if (parent instanceof ParameterList) { + return ArrayUtil.indexOf(((ParameterList) parent).getParameters(), psiElement); + } + + return paramIndexForElement(parent); + } + static Collection getClassFields(PhpClass phpClass) { + if (phpClass == null) + return null; final HashSet result = new HashSet<>(); final Collection fields = phpClass.getFields(); @@ -216,26 +240,9 @@ static Collection getClassFields(PhpClass phpClass) { } + + return result; } - static PhpClass getStandardPhpClass(PhpIndex phpIndex, String shortName) { - switch (shortName){ - // web/Application - case "request": return getClass(phpIndex, "\\yii\\web\\Request"); - case "response": return getClass(phpIndex, "\\yii\\web\\Response"); - case "session": return getClass(phpIndex, "\\yii\\web\\Session"); - case "user": return getClass(phpIndex, "\\yii\\web\\User"); - case "errorHandler": return getClass(phpIndex, "\\yii\\web\\ErrorHandler"); - // base/Application - case "log": return getClass(phpIndex, "\\yii\\log\\Dispatcher"); - case "view": return getClass(phpIndex, "\\yii\\web\\View"); - case "formatter": return getClass(phpIndex, "\\yii\\i18n\\I18N"); - case "mailer": return getClass(phpIndex, "\\yii\\swiftmailer\\Mailer"); - case "urlManager": return getClass(phpIndex, "\\yii\\web\\UrlManager"); - case "assetManager": return getClass(phpIndex, "\\yii\\web\\AssetManager"); - case "security": return getClass(phpIndex, "\\yii\\base\\Security"); - } - return null; - } } diff --git a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryMissedFieldInspection.java b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryMissedFieldInspection.java index fbf19bdc..e6bbf2c0 100644 --- a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryMissedFieldInspection.java +++ b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryMissedFieldInspection.java @@ -36,7 +36,7 @@ public void visitPhpArrayCreationExpression(ArrayCreationExpression expression) String keyName = elem.getKey() != null ? elem.getKey().getText() : null; keyName = ClassUtils.removeQuotes(keyName); if ( keyName != null && ! keyName.equals("class") && ClassUtils.findField(phpClass, keyName) == null) { - final String descriptionTemplate = "Field not exists in referenced class"; + final String descriptionTemplate = "Field '"+keyName+"' not exists in referenced class "+phpClass.getFQN(); problemsHolder.registerProblem(elem, descriptionTemplate); } } diff --git a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceContributor.java b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceContributor.java index 3872be7e..82ed1065 100644 --- a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceContributor.java +++ b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceContributor.java @@ -19,7 +19,6 @@ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferen private static ElementPattern ElementPattern() { return PlatformPatterns.psiElement() - .withParent(PlatformPatterns.or( PlatformPatterns.psiElement().withParent(ArrayCreationExpression.class), Patterns.withHashKey() diff --git a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceProvider.java b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceProvider.java index 8f4710bd..282f55b5 100644 --- a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceProvider.java +++ b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryReferenceProvider.java @@ -1,6 +1,7 @@ package com.nvlad.yii2support.objectfactory; import com.intellij.psi.PsiElement; +import com.intellij.psi.css.CssFileType; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; @@ -20,6 +21,7 @@ public ObjectFactoryReference[] getReferencesByElement(@NotNull PsiElement psiEl ObjectFactoryReference reference = new ObjectFactoryReference(psiElement); references.add(reference); + return references.toArray(new ObjectFactoryReference[references.size()]); } } diff --git a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryUtils.java b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryUtils.java index 8b85c004..8425b4c5 100644 --- a/src/com/nvlad/yii2support/objectfactory/ObjectFactoryUtils.java +++ b/src/com/nvlad/yii2support/objectfactory/ObjectFactoryUtils.java @@ -1,16 +1,17 @@ package com.nvlad.yii2support.objectfactory; -import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.generation.ClassMember; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.jetbrains.php.PhpIndex; -import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.naming.spi.ObjectFactory; import java.util.HashMap; +import java.util.Set; /** * Created by oleg on 14.03.2017. @@ -54,7 +55,7 @@ static PhpClass getPhpClassByYiiCreateObject(ArrayCreationExpression arrayCreati PhpExpression methodClass = method.getClassReference(); if (methodClass != null && methodClass.getName() != null && methodClass.getName().equals("Yii")) { PsiElement[] pList = method.getParameters(); - if (pList.length == 2) { // \Yii::createObject takes 2 paramters + if (pList.length == 2 && ClassUtils.paramIndexForElement(arrayCreation) == 1) { // \Yii::createObject takes 2 paramters phpClass = ClassUtils.getPhpClassUniversal(method.getProject(), (PhpPsiElement) pList[0]); } } @@ -65,7 +66,7 @@ static PhpClass getPhpClassByYiiCreateObject(ArrayCreationExpression arrayCreati static PhpClass getPhpClassInConfig(PsiDirectory dir, ArrayCreationExpression arrayCreation) { PhpClass phpClass = null; - if (dir != null && dir.getName().equals("config")) { + if (dir != null && (dir.getName().equals("config") || dir.getName().equals("src") /* for tests */)) { PsiElement parent = arrayCreation.getParent().getParent(); if (parent instanceof ArrayHashElement) { ArrayHashElement hash = (ArrayHashElement) parent; @@ -73,7 +74,7 @@ static PhpClass getPhpClassInConfig(PsiDirectory dir, ArrayCreationExpression ar if (element instanceof StringLiteralExpression) { StringLiteralExpression literal = (StringLiteralExpression) element; String key = literal.getContents(); - phpClass = ClassUtils.getStandardPhpClass(PhpIndex.getInstance(literal.getProject()), key); + phpClass = getStandardPhpClass(PhpIndex.getInstance(literal.getProject()), key); } } } @@ -83,13 +84,32 @@ static PhpClass getPhpClassInConfig(PsiDirectory dir, ArrayCreationExpression ar static PhpClass getPhpClassInWidget(ArrayCreationExpression arrayCreation) { PsiElement parent = arrayCreation.getParent().getParent(); if (parent != null && parent instanceof MethodReference) { - MethodReference method = (MethodReference) parent; - if (method.getName() != null && (method.getName().equals("widget") || method.getName().equals("begin"))) { - PhpExpression methodClass = method.getClassReference(); - PhpClass callingClass = (PhpClass) ((ClassReference) methodClass).resolve(); - PhpClass superClass = ClassUtils.getClass(PhpIndex.getInstance(methodClass.getProject()), "\\yii\\base\\Widget"); - if (ClassUtils.isClassInherits(callingClass, superClass)) - return callingClass; + MethodReference methodRef = (MethodReference) parent; + if (methodRef.getName() != null && (methodRef.getName().equals("widget") || methodRef.getName().equals("begin"))) { + Method method = (Method)methodRef.resolve(); + + PhpExpression ref = methodRef.getClassReference(); + if (ref != null && ref instanceof ClassReference && ClassUtils.paramIndexForElement(arrayCreation) == 0) { + PhpClass callingClass = (PhpClass) ((ClassReference) ref).resolve(); + PhpClass superClass = ClassUtils.getClass(PhpIndex.getInstance(methodRef.getProject()), "\\yii\\base\\Widget"); + if (ClassUtils.isClassInheritsOrEqual(callingClass, superClass)) + return callingClass; + } else if (ref != null && ref instanceof MethodReference && ClassUtils.paramIndexForElement(arrayCreation) == 1 ) { + // This code process + // $form->field($model, 'username')->widget(\Class::className()) + PhpClass callingClass = method.getContainingClass(); + PhpClass superClass = ClassUtils.getClass(PhpIndex.getInstance(methodRef.getProject()), "yii\\widgets\\ActiveField"); + if (ClassUtils.isClassInheritsOrEqual(callingClass, superClass) + && method.getParameters().length == 2 && + method.getParameters()[0].getName().equals("class")) { + PhpPsiElement element = (PhpPsiElement)methodRef.getParameters()[0]; + PhpClass widgetClass = ClassUtils.getPhpClassUniversal(methodRef.getProject(), element); + if (widgetClass != null) + return widgetClass; + + } + + } } } @@ -123,23 +143,100 @@ static PhpClass getPhpClassInGridColumns(ArrayCreationExpression arrayCreation) return null; } + @Nullable static PhpClass findClassByArrayCreation(ArrayCreationExpression arrayCreation, PsiDirectory dir) { PhpClass phpClass; phpClass = findClassByArray(arrayCreation); - if (phpClass == null) { - phpClass = getPhpClassByYiiCreateObject(arrayCreation); + if (phpClass == null){ + phpClass = getClassByInstatiation(arrayCreation); } if (phpClass == null) { - - phpClass = getPhpClassInConfig(dir, arrayCreation); + phpClass = getPhpClassByYiiCreateObject(arrayCreation); } - if (phpClass == null) { phpClass = getPhpClassInWidget(arrayCreation); } if (phpClass == null) { phpClass = getPhpClassInGridColumns(arrayCreation); } + if (phpClass == null && arrayCreation.getParent().getParent() instanceof ArrayHashElement) { + phpClass = getPhpClassByHash((ArrayHashElement)arrayCreation.getParent().getParent(), dir); + } + if (phpClass == null) { + phpClass = getPhpClassInConfig(dir, arrayCreation); + } return phpClass; } + + private static PhpClass getPhpClassByHash(ArrayHashElement hashElement, PsiDirectory dir) { + if (hashElement.getParent() instanceof ArrayCreationExpression) { + PhpClass phpClass = findClassByArrayCreation((ArrayCreationExpression)hashElement.getParent(), dir); + if (phpClass == null) + return null; + String fieldName = hashElement.getKey() != null ? hashElement.getKey().getText() : null; + if (fieldName == null) + return null; + PhpClassMember field = ClassUtils.findField(phpClass, fieldName); + if (field == null) + return null; + Set types = field.getType().getTypes(); + PhpClass resultClass = null; + for (String type : types) { + resultClass = ClassUtils.getClass(PhpIndex.getInstance(field.getProject()), type); + if (resultClass != null) { + return resultClass; + } + } + } + return null; + } + + static PhpClass getClassByInstatiation(PhpExpression element) { + + PsiElement newElement = element.getParent().getParent(); + if (newElement != null && newElement instanceof NewExpression) { + ClassReference ref = ((NewExpression) newElement).getClassReference(); + if (ref == null) + return null; + PhpClass phpClass =(PhpClass)ref.resolve(); + if (phpClass != null) { + + Method constructor = phpClass.getConstructor(); + + PhpClass yiiObjectClass = ClassUtils.getClass(PhpIndex.getInstance(element.getProject()), "\\yii\\base\\Object"); + if (! ClassUtils.isClassInheritsOrEqual(phpClass, yiiObjectClass)) + return null; + + Parameter[] parameterList = constructor.getParameters(); + if (parameterList.length >0 && parameterList[0].getName().equals("config") && ClassUtils.paramIndexForElement(element) == 0) + return phpClass; + + } + } + return null; + } + + + static PhpClass getStandardPhpClass(PhpIndex phpIndex, String shortName) { + switch (shortName){ + // web/Application + case "request": return ClassUtils.getClass(phpIndex, "\\yii\\web\\Request"); + case "response": return ClassUtils.getClass(phpIndex, "\\yii\\web\\Response"); + case "session": return ClassUtils.getClass(phpIndex, "\\yii\\web\\Session"); + case "user": return ClassUtils.getClass(phpIndex, "\\yii\\web\\User"); + case "errorHandler": return ClassUtils.getClass(phpIndex, "\\yii\\web\\ErrorHandler"); + // base/Application + case "log": return ClassUtils.getClass(phpIndex, "\\yii\\log\\Dispatcher"); + case "view": return ClassUtils.getClass(phpIndex, "\\yii\\web\\View"); + case "formatter": return ClassUtils.getClass(phpIndex, "yii\\i18n\\Formatter"); + case "i18n": return ClassUtils.getClass(phpIndex, "yii\\i18n\\I18N"); + case "mailer": return ClassUtils.getClass(phpIndex, "\\yii\\swiftmailer\\Mailer"); + case "urlManager": return ClassUtils.getClass(phpIndex, "\\yii\\web\\UrlManager"); + case "assetManager": return ClassUtils.getClass(phpIndex, "\\yii\\web\\AssetManager"); + case "security": return ClassUtils.getClass(phpIndex, "\\yii\\base\\Security"); + // custom + case "db": return ClassUtils.getClass(phpIndex, "\\yii\\db\\Connection"); + } + return null; + } } diff --git a/src/com/nvlad/yii2support/objectfactory/README.md b/src/com/nvlad/yii2support/objectfactory/README.md index 0b04c60a..0c26f263 100644 --- a/src/com/nvlad/yii2support/objectfactory/README.md +++ b/src/com/nvlad/yii2support/objectfactory/README.md @@ -2,4 +2,29 @@ Code completion of keys in arrays that have "class" key and valid class referenc Following class reference representations supported: - String represention, - Class::class - - Class::className(). \ No newline at end of file + - Class::className(). + + Code completion also works for standard classes (detected by key) in config directory. + + widget() and begin() method for \yii\base\Widget class descendants is supported. + + widget() method for \yii\widgets\ActiveField is supported + + Code completion for GridView columns is supported + + This module supports Go To Declaration, Rename, Find usages + + ------------------------------------ + Configuration arrays + + Code completion for Yii configuration arrays. Works both in configuration files and on object instantiation. + Following cases are supported: + * Array in $config parameter in yii\base\Object or its descendants constructor + * Array have "class" key with valid class representation: fully qualified string representation, ClassName::class or Class::className() + * Array is a value of a key that corresponds to standard Yii classes (like "db", "request", "mailer" and so on), and file with this array located in a directory called "config" + * WidgetClass::widget() and WidgetClass::begin calls if WidgetClass is a descendant of yii\base\Widget + * $field->widget() method call on yii\widgets\ActiveField and its descendants + * Inside array in GridView "columns" key + + +Go To Declaration, Rename, Find usages and Help popup works whenever code completion works \ No newline at end of file diff --git a/tests/com/nvlad/yii2support/PluginTestCase.java b/tests/com/nvlad/yii2support/PluginTestCase.java new file mode 100644 index 00000000..2458fae2 --- /dev/null +++ b/tests/com/nvlad/yii2support/PluginTestCase.java @@ -0,0 +1,14 @@ +package com.nvlad.yii2support; + +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; + +/** + * Created by oleg on 16.03.2017. + */ +public class PluginTestCase extends LightCodeInsightFixtureTestCase { + public void assertCompletionResultEquals(String filename, String complete, String result) { + myFixture.configureByText(filename, complete); + myFixture.completeBasic(); + myFixture.checkResult(result); + } +} diff --git a/tests/com/nvlad/yii2support/objectfactory/ObjectFactoryTests.java b/tests/com/nvlad/yii2support/objectfactory/ObjectFactoryTests.java new file mode 100644 index 00000000..c27703eb --- /dev/null +++ b/tests/com/nvlad/yii2support/objectfactory/ObjectFactoryTests.java @@ -0,0 +1,90 @@ +package com.nvlad.yii2support.objectfactory; + + +import com.jetbrains.php.lang.PhpFileType; +import com.nvlad.yii2support.PluginTestCase; + +import java.io.File; + + +/** + * Created by oleg on 16.03.2017.. + */ +public class ObjectFactoryTests extends PluginTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("classes.php")); + + } + + @Override + protected String getTestDataPath() { + return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath(); + + } + + public void testCompletionWidget_widget() { + + myFixture.configureByText(PhpFileType.INSTANCE, "']) ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 2); + } + + public void testCompletionWidget_begin() { + + myFixture.configureByText(PhpFileType.INSTANCE, "']) ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 2); + } + + public void testCompletionObject_create() { + + myFixture.configureByText(PhpFileType.INSTANCE, "']) ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 2); + } + + public void testCompletion_createObject() { + + myFixture.configureByText(PhpFileType.INSTANCE, "']) ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 2); + } + + public void testCompletionInConfigAndSubObject() { + + myFixture.configureByText(PhpFileType.INSTANCE, " [ 'subobject' => ['']] ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 3); + } + + public void testCompletionYii_createObject() { + + myFixture.configureByText(PhpFileType.INSTANCE, "']) ;\n" + + ";"); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 3); + } + + public void testCompletionYii_gridColumns() { + + myFixture.configureByText(PhpFileType.INSTANCE, " [['']] "); + myFixture.completeBasic(); + assertEquals(myFixture.getLookupElementStrings().toArray().length, 2); + } + +} diff --git a/tests/com/nvlad/yii2support/objectfactory/fixtures/classes.php b/tests/com/nvlad/yii2support/objectfactory/fixtures/classes.php new file mode 100644 index 00000000..5327fb62 --- /dev/null +++ b/tests/com/nvlad/yii2support/objectfactory/fixtures/classes.php @@ -0,0 +1,84 @@ + +