Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -735,15 +735,14 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull

// find core function for that
for(TwigTypeContainer twigTypeContainer: TwigTypeResolveUtil.resolveTwigMethodName(psiElement, possibleTypes)) {
if(twigTypeContainer.getPhpNamedElement() instanceof PhpClass) {

for(Method method: ((PhpClass) twigTypeContainer.getPhpNamedElement()).getMethods()) {
for (PhpClass phpClass : TwigTypeResolveUtil.resolveTwigTypeClasses(psiElement.getProject(), twigTypeContainer)) {
for(Method method: phpClass.getMethods()) {
if(TwigTypeResolveUtil.isTwigAccessibleMethod(method)) {
resultSet.addElement(new PhpTwigMethodLookupElement(method));
}
}

for(Field field: ((PhpClass) twigTypeContainer.getPhpNamedElement()).getFields()) {
for(Field field: phpClass.getFields()) {
if(field.getModifier().isPublic()) {
resultSet.addElement(new PhpTwigMethodLookupElement(field));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,7 @@ public static Collection<PsiElement> getTypeGoto(@NotNull PsiElement psiElement)
if(beforeLeaf.isEmpty()) {
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeResolveUtil.resolveTwigMethodName(psiElement, TwigTypeResolveUtil.formatPsiTypeNameWithCurrent(psiElement));
for(TwigTypeContainer twigTypeContainer: twigTypeContainers) {
if(twigTypeContainer.getPhpNamedElement() != null) {
targetPsiElements.add(twigTypeContainer.getPhpNamedElement());
}
targetPsiElements.addAll(TwigTypeResolveUtil.resolveTwigTypeClasses(psiElement.getProject(), twigTypeContainer));
}

} else {
Expand All @@ -531,9 +529,7 @@ public static Collection<PsiElement> getTypeGoto(@NotNull PsiElement psiElement)
if(StringUtils.isNotBlank(text)) {
// provide method / field goto
for(TwigTypeContainer twigTypeContainer: types) {
if(twigTypeContainer.getPhpNamedElement() != null) {
targetPsiElements.addAll(TwigTypeResolveUtil.getTwigPhpNameTargets(twigTypeContainer.getPhpNamedElement(), text));
}
targetPsiElements.addAll(TwigTypeResolveUtil.getTwigPhpNameTargets(psiElement.getProject(), twigTypeContainer, text));

// form
// @TODO: provide extension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NonNull;

import java.util.Collection;

Expand Down Expand Up @@ -43,35 +44,33 @@ private static class MyPsiElementVisitor extends PsiElementVisitor {
}

@Override
public void visitElement(PsiElement element) {
if(getTypeCompletionPattern().accepts(element)) {
public void visitElement(@NonNull PsiElement element) {
if (getTypeCompletionPattern().accepts(element)) {
visit(element);
}

super.visitElement(element);
}

private void visit(@NotNull PsiElement element) {
Collection<String> beforeLeaf = TwigTypeResolveUtil.formatPsiTypeName(element);
if(beforeLeaf.isEmpty()) {
if (beforeLeaf.isEmpty()) {
return;
}

Collection<TwigTypeContainer> types = TwigTypeResolveUtil.resolveTwigMethodName(element, beforeLeaf);
if(types.isEmpty()) {
if (types.isEmpty()) {
return;
}

for(TwigTypeContainer twigTypeContainer: types) {
PhpNamedElement phpClass = twigTypeContainer.getPhpNamedElement();
if(!(phpClass instanceof PhpClass)) {
continue;
}

String text = element.getText();
for (TwigTypeContainer twigTypeContainer: types) {
for (PhpClass phpClass : TwigTypeResolveUtil.resolveTwigTypeClasses(element.getProject(), twigTypeContainer)) {
String text = element.getText();

for (PhpNamedElement namedElement : TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text)) {
if(namedElement instanceof Method method && PhpElementsUtil.isClassOrFunctionDeprecated(method)) {
this.holder.registerProblem(element, String.format("Method '%s::%s' is deprecated", phpClass.getName(), namedElement.getName()), ProblemHighlightType.LIKE_DEPRECATED);
for (PhpNamedElement namedElement : TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text)) {
if (namedElement instanceof Method method && PhpElementsUtil.isClassOrFunctionDeprecated(method)) {
this.holder.registerProblem(element, String.format("Method '%s::%s' is deprecated", phpClass.getName(), namedElement.getName()), ProblemHighlightType.LIKE_DEPRECATED);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern;
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigTypeResolveUtil;
import fr.adrienbrault.idea.symfony2plugin.templating.variable.TwigTypeContainer;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NonNull;

import java.util.Collection;

Expand Down Expand Up @@ -42,7 +41,7 @@ private static class MyPsiElementVisitor extends PsiElementVisitor {
}

@Override
public void visitElement(PsiElement element) {
public void visitElement(@NonNull PsiElement element) {
if(getTypeCompletionPattern().accepts(element)) {
visit(element);
}
Expand All @@ -56,36 +55,31 @@ private void visit(@NotNull PsiElement element) {
}

Collection<TwigTypeContainer> types = TwigTypeResolveUtil.resolveTwigMethodName(element, beforeLeaf);
if(types.isEmpty()) {
if (types.isEmpty()) {
return;
}

for(TwigTypeContainer twigTypeContainer: types) {
PhpNamedElement phpNamedElement = twigTypeContainer.getPhpNamedElement();
if(phpNamedElement == null) {
continue;
}

if(isWeakPhpClass(phpNamedElement)) {
for (TwigTypeContainer twigTypeContainer: types) {
String text = element.getText();
Collection<PhpClass> phpClasses = TwigTypeResolveUtil.resolveTwigTypeClasses(element.getProject(), twigTypeContainer);
if (phpClasses.isEmpty()) {
return;
}

String text = element.getText();
if(!TwigTypeResolveUtil.getTwigPhpNameTargets(phpNamedElement, text).isEmpty()) {
return;
for (PhpClass phpClass : phpClasses) {
if(TwigTypeResolveUtil.isWeakCollectionLikeClass(phpClass)) {
return;
}

if (!TwigTypeResolveUtil.getTwigPhpNameTargets(phpClass, text).isEmpty()) {
return;
}
}
}

this.holder.registerProblem(element, "Field or method not found", ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}

private boolean isWeakPhpClass(PhpNamedElement phpNamedElement) {
return phpNamedElement instanceof PhpClass && (
PhpElementsUtil.isInstanceOf((PhpClass) phpNamedElement, "ArrayAccess") ||
PhpElementsUtil.isInstanceOf((PhpClass) phpNamedElement, "Iterator")
);
}

private ElementPattern<PsiElement> getTypeCompletionPattern() {
return typeCompletionPattern != null ? typeCompletionPattern : (typeCompletionPattern = TwigPattern.getTypeCompletionPattern());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull PsiEl
Collection<PsiVariable> rootVariables = getRootVariableByName(psiElement, rootType);
if (types.size() == 1) {
Project project = psiElement.getProject();
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeContainer.fromCollection(project, rootVariables);
Collection<TwigTypeContainer> twigTypeContainers = TwigTypeContainer.fromCollection(rootVariables);
for(TwigTypeResolver twigTypeResolver: TWIG_TYPE_RESOLVERS) {
twigTypeResolver.resolve(project, twigTypeContainers, twigTypeContainers, rootType, new ArrayList<>(), rootVariables);
}
Expand All @@ -154,7 +154,7 @@ public static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull PsiEl
}

Project project = psiElement.getProject();
Collection<TwigTypeContainer> type = TwigTypeContainer.fromCollection(project, rootVariables);
Collection<TwigTypeContainer> type = TwigTypeContainer.fromCollection(rootVariables);
Collection<List<TwigTypeContainer>> previousElements = new ArrayList<>();
previousElements.add(new ArrayList<>(type));

Expand Down Expand Up @@ -546,23 +546,18 @@ private static Collection<TwigTypeContainer> resolveTwigMethodName(@NotNull Proj

for(TwigTypeContainer phpNamedElement: previousElement) {

if(phpNamedElement.getPhpNamedElement() != null) {
for(PhpNamedElement target : getTwigPhpNameTargets(phpNamedElement.getPhpNamedElement(), typeName)) {
PhpType phpType = target.getType();
for(PhpNamedElement target : getTwigPhpNameTargets(project, phpNamedElement, typeName)) {
PhpType phpType = target.getType();

// @TODO: provide extension
// custom resolving for Twig here: "app.user" => can also be a general solution just support the "getToken()->getUser()"
if (target instanceof Method && StaticVariableCollector.isUserMethod((Method) target)) {
phpNamedElements.addAll(getApplicationUserImplementations(target.getProject()));
}
// @TODO: provide extension
// custom resolving for Twig here: "app.user" => can also be a general solution just support the "getToken()->getUser()"
if (target instanceof Method && StaticVariableCollector.isUserMethod((Method) target)) {
phpNamedElements.addAll(getApplicationUserImplementations(project));
}

// @TODO: use full resolving for object, that would allow using TypeProviders and core PhpStorm feature
for (String typeString: phpType.filterPrimitives().getTypes()) {
PhpClass phpClass = PhpElementsUtil.getClassInterface(phpNamedElement.getPhpNamedElement().getProject(), typeString);
if(phpClass != null) {
phpNamedElements.add(new TwigTypeContainer(phpClass));
}
}
Set<String> types = phpType.filterPrimitives().getTypes();
if (!types.isEmpty()) {
phpNamedElements.add(new TwigTypeContainer(types));
}
}

Expand All @@ -584,7 +579,7 @@ private static Collection<TwigTypeContainer> getApplicationUserImplementations(@
.getAllSubclasses(project, "\\Symfony\\Component\\Security\\Core\\User\\UserInterface")
.stream()
.filter(phpClass -> !phpClass.isInterface()) // filter out implementation like AdvancedUserInterface
.map(TwigTypeContainer::new)
.map(phpClass -> new TwigTypeContainer(Collections.singleton(phpClass.getFQN())))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -635,6 +630,26 @@ public static Collection<? extends PhpNamedElement> getTwigPhpNameTargets(PhpNam
return targets;
}

@NotNull
public static Collection<PhpClass> resolveTwigTypeClasses(@NotNull Project project, @NotNull TwigTypeContainer twigTypeContainer) {
if (twigTypeContainer.getTypes().isEmpty()) {
return Collections.emptyList();
}

return PhpElementsUtil.getClassFromPhpTypeSet(project, twigTypeContainer.getTypes());
}

@NotNull
public static Collection<? extends PhpNamedElement> getTwigPhpNameTargets(@NotNull Project project, @NotNull TwigTypeContainer twigTypeContainer, @NotNull String variableName) {
Collection<PhpNamedElement> targets = new ArrayList<>();

for (PhpClass phpClass : resolveTwigTypeClasses(project, twigTypeContainer)) {
targets.addAll(getTwigPhpNameTargets(phpClass, variableName));
}

return targets;
}


public static String getTypeDisplayName(Project project, Set<String> types) {

Expand Down Expand Up @@ -685,6 +700,10 @@ public static boolean isTwigAccessibleMethod(@NotNull Method method) {
return !name.startsWith("set") && !name.startsWith("__");
}

public static boolean isWeakCollectionLikeClass(@NotNull PhpClass phpClass) {
return PhpElementsUtil.isInstanceOf(phpClass, "ArrayAccess") || PhpElementsUtil.isInstanceOf(phpClass, "Iterator");
}

public static boolean isPropertyShortcutMethod(String methodName) {
for (String shortcut: PROPERTY_SHORTCUTS) {
if (methodName.startsWith(shortcut) && methodName.length() > shortcut.length()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
package fr.adrienbrault.idea.symfony2plugin.templating.variable;

import com.intellij.openapi.project.Project;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
import fr.adrienbrault.idea.symfony2plugin.templating.variable.dict.PsiVariable;
import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormFieldDataHolder;
import fr.adrienbrault.idea.symfony2plugin.templating.variable.resolver.holder.FormViewDataHolder;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
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.HashSet;
import java.util.List;
import java.util.Set;

/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class TwigTypeContainer {

private final PhpNamedElement phpNamedElement;
private final Set<String> types = new HashSet<>();
private final String stringElement;
private final FormFieldDataHolder formFieldDataHolder;
private final FormViewDataHolder formViewDataHolder;

public TwigTypeContainer(PhpNamedElement phpNamedElement) {
this(phpNamedElement, null);
public TwigTypeContainer(@NotNull Collection<String> types) {
this(types, null);
}

public TwigTypeContainer(PhpNamedElement phpNamedElement, @Nullable FormViewDataHolder formViewDataHolder) {
this.phpNamedElement = phpNamedElement;
private TwigTypeContainer(@NotNull Collection<String> types, @Nullable FormViewDataHolder formViewDataHolder) {
this.types.addAll(types);
this.stringElement = null;
this.formFieldDataHolder = null;
this.formViewDataHolder = formViewDataHolder;
Expand All @@ -39,35 +39,35 @@ public TwigTypeContainer(String stringElement) {
}

public TwigTypeContainer(String stringElement, @Nullable FormFieldDataHolder formFieldDataHolder) {
this.phpNamedElement = null;
this.stringElement = stringElement;
this.formFieldDataHolder = formFieldDataHolder;
this.formViewDataHolder = null;
}

@Nullable
public PhpNamedElement getPhpNamedElement() {
return phpNamedElement;
@NotNull
public Set<String> getTypes() {
return Collections.unmodifiableSet(types);
}

@Nullable
public String getStringElement() {
return stringElement;
}

public static Collection<TwigTypeContainer> fromCollection(Project project, Collection<PsiVariable> psiVariables) {
public static Collection<TwigTypeContainer> fromCollection(Collection<PsiVariable> psiVariables) {

List<TwigTypeContainer> twigTypeContainerList = new ArrayList<>();

for(PsiVariable phpNamedElement :psiVariables) {
Collection<PhpClass> phpClass = PhpElementsUtil.getClassFromPhpTypeSet(project, phpNamedElement.getTypes());
if(!phpClass.isEmpty()) {
FormViewDataHolder formViewDataHolder = phpNamedElement.getFormTypeFqns().isEmpty()
? null
: new FormViewDataHolder(phpNamedElement.getFormTypeFqns());

twigTypeContainerList.add(new TwigTypeContainer(phpClass.iterator().next(), formViewDataHolder));
for(PsiVariable psiVariable : psiVariables) {
if (psiVariable.getTypes().isEmpty()) {
continue;
}

FormViewDataHolder formViewDataHolder = psiVariable.getFormTypeFqns().isEmpty()
? null
: new FormViewDataHolder(psiVariable.getFormTypeFqns());

twigTypeContainerList.add(new TwigTypeContainer(psiVariable.getTypes(), formViewDataHolder));
}

return twigTypeContainerList;
Expand Down
Loading
Loading