Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2348 provde FQCN-based Routes support (only compiled provider) / [Twig] Escaping route names with backslashes #2374

Merged
merged 1 commit into from
May 20, 2024
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 @@ -115,6 +115,22 @@ public static Collection<Route> getRoute(@NotNull Project project, @NotNull Stri
return routes;
}

public static boolean isExistingRouteName(@NotNull Project project, @NotNull String routeName) {
return getFQCNRoute(project, routeName) != null
|| !getRoute(project, routeName).isEmpty();
}

/**
* "App\\Controller\\Car\\ZipController::index" => "App\Controller\Car\ZipController::index"
*/
public static String unescapeRouteName(@NotNull String routeName) {
if (routeName.contains("\\")) {
return routeName.replace("\\\\", "\\");
}

return routeName;
}

public static PsiElement[] getRouteParameterPsiElements(@NotNull Project project, @NotNull String routeName, @NotNull String parameterName) {
Collection<PsiElement> results = new ArrayList<>();

Expand All @@ -134,13 +150,46 @@ public static PsiElement[] getRouteParameterPsiElements(@NotNull Project project
public static PsiElement[] getMethods(@NotNull Project project, @NotNull String routeName) {
Set<PsiElement> targets = new HashSet<>();

Method fqcnRoute = getFQCNRoute(project, routeName);
if (fqcnRoute != null) {
targets.add(fqcnRoute);
}

for (Route route : getRoute(project, routeName)) {
targets.addAll(Arrays.asList(getMethodsOnControllerShortcut(project, route.getController())));
}

return targets.toArray(new PsiElement[0]);
}

/**
* "App\Controller\Foo"
* "App\Controller\Foo::method"
*/
public static Method getFQCNRoute(@NotNull Project project, @NotNull String routeName) {
if (!routeName.contains("\\")) {
return null;
}

String className = null;
String method = null;

if (routeName.contains(":")) {
String[] split = routeName.split("::");
if (split.length == 2) {
className = "\\" + StringUtils.stripStart(split[0], "\\");
method = split[1];
}
} else {
className = "\\" + StringUtils.stripStart(routeName, "\\");
method = "__invoke";
}

return className != null && method != null
? PhpElementsUtil.getClassMethod(project, className, method)
: null;
}

public static Collection<Route> findRoutesByPath(@NotNull Project project, @NotNull String path) {
return RouteHelper.getAllRoutes(project)
.values()
Expand Down Expand Up @@ -1285,15 +1334,19 @@ public static String getRouteUrl(Route route) {
return url.isEmpty() ? null : url;
}

public static List<LookupElement> getRoutesLookupElements(final @NotNull Project project) {

public static List<LookupElement> getRoutesLookupElements(final @NotNull Project project, boolean escapeRouteName) {
Map<String, Route> routes = RouteHelper.getCompiledRoutes(project);

final List<LookupElement> lookupElements = new ArrayList<>();

final Set<String> uniqueSet = new HashSet<>();
for (Route route : routes.values()) {
lookupElements.add(new RouteLookupElement(route));
RouteLookupElement lookupElement = new RouteLookupElement(route);
if (escapeRouteName) {
lookupElement.withEscape();
}

lookupElements.add(lookupElement);
uniqueSet.add(route.getName());
}

Expand All @@ -1303,13 +1356,21 @@ public static List<LookupElement> getRoutesLookupElements(final @NotNull Project
}

for (StubIndexedRoute route: FileBasedIndex.getInstance().getValues(RoutesStubIndex.KEY, routeName, GlobalSearchScope.allScope(project))) {
lookupElements.add(new RouteLookupElement(new Route(route), true));
RouteLookupElement lookupElement = new RouteLookupElement(new Route(route), true);
if (escapeRouteName) {
lookupElement.withEscape();
}

lookupElements.add(lookupElement);
uniqueSet.add(routeName);
}
}

return lookupElements;
}

public static List<LookupElement> getRoutesLookupElements(final @NotNull Project project) {
return getRoutesLookupElements(project, false);
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class RouteLookupElement extends LookupElement {
final private Route route;
private boolean isWeak = false;
private InsertHandler<RouteLookupElement> insertHandler;
private boolean escape = false;

public RouteLookupElement(@NotNull Route route) {
this.route = route;
Expand All @@ -36,6 +37,10 @@ public RouteLookupElement(@NotNull Route route, boolean isWeak) {
@NotNull
@Override
public String getLookupString() {
if (escape) {
return route.getName().replace("\\", "\\\\");
}

return route.getName();
}

Expand Down Expand Up @@ -81,6 +86,10 @@ public void withInsertHandler(InsertHandler<RouteLookupElement> insertHandler) {
this.insertHandler = insertHandler;
}

public void withEscape() {
this.escape = true;
}

public Route getRoute() {
return route;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
import com.intellij.psi.PsiElementVisitor;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.routing.PhpRouteReferenceContributor;
import fr.adrienbrault.idea.symfony2plugin.routing.Route;
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;

/**
* @author Daniel Espendiller <[email protected]>
*/
Expand Down Expand Up @@ -51,8 +48,7 @@ private void invoke(@NotNull String routeName, @NotNull final PsiElement element
return;
}

Collection<Route> route = RouteHelper.getRoute(element.getProject(), routeName);
if(route.isEmpty()) {
if (!RouteHelper.isExistingRouteName(element.getProject(), routeName)) {
holder.registerProblem(element, "Symfony: Missing Route", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RouteGuessTypoQuickFix(routeName));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool

return new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
public void visitElement(@NotNull PsiElement element) {
if(TwigPattern.getAutocompletableRoutePattern().accepts(element) && TwigUtil.isValidStringWithoutInterpolatedOrConcat(element)) {
invoke(element, holder);
}
Expand All @@ -35,11 +35,11 @@ public void visitElement(PsiElement element) {

private void invoke(@NotNull final PsiElement element, @NotNull ProblemsHolder holder) {
String text = element.getText();
if(StringUtils.isBlank(text)) {
if (StringUtils.isBlank(text)) {
return;
}

if(RouteHelper.getRoute(element.getProject(), text).isEmpty()) {
if (!RouteHelper.isExistingRouteName(element.getProject(), RouteHelper.unescapeRouteName(text))) {
holder.registerProblem(element, "Symfony: Missing Route", new RouteGuessTypoQuickFix(text));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ public void addCompletions(@NotNull CompletionParameters parameters, @NotNull Pr
return;
}

resultSet.addAllElements(RouteHelper.getRoutesLookupElements(parameters.getPosition().getProject()));
resultSet.addAllElements(RouteHelper.getRoutesLookupElements(parameters.getPosition().getProject(), true));
}
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,12 @@ public static Collection<PsiElement> getFilterGoTo(@NotNull PsiElement psiEleme
private Collection<PsiElement> getRouteGoTo(@NotNull PsiElement psiElement) {
String text = PsiElementUtils.getText(psiElement);

if(StringUtils.isBlank(text)) {
if (StringUtils.isBlank(text)) {
return Collections.emptyList();
}

PsiElement[] methods = RouteHelper.getMethods(psiElement.getProject(), text);
if(methods.length > 0) {
PsiElement[] methods = RouteHelper.getMethods(psiElement.getProject(), RouteHelper.unescapeRouteName(text));
if (methods.length > 0) {
return Arrays.asList(methods);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, Processi
return;
}

List<LookupElement> routesLookupElements = RouteHelper.getRoutesLookupElements(parameters.getPosition().getProject());
List<LookupElement> routesLookupElements = RouteHelper.getRoutesLookupElements(parameters.getPosition().getProject(), true);
for (LookupElement element : routesLookupElements) {
if (element instanceof RouteLookupElement) {
((RouteLookupElement) element).withInsertHandler(TwigPathFunctionInsertHandler.getInstance());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public void setUp() throws Exception {
super.setUp();

myFixture.copyFileToProject("TwigRouteMissingInspection.xml");
myFixture.copyFileToProject("TwigRouteMissingInspection.php");
}

protected String getTestDataPath() {
Expand All @@ -31,6 +32,18 @@ public void testThatKnownRouteMustNotProvideErrorHighlight() {
"{{ path('my_<caret>foobar') }}",
"Symfony: Missing Route"
);

assertLocalInspectionNotContains(
"test.html.twig",
"{{ path('App\\\\Controller\\\\Foobar<caret>Controller') }}",
"Symfony: Missing Route"
);

assertLocalInspectionNotContains(
"test.html.twig",
"{{ path('App\\\\Controller\\\\FooCon<caret>troller::foobar') }}",
"Symfony: Missing Route"
);
}

public void testThatInterpolatedStringMustBeIgnoredForInspection() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Controller
{
class FooController
{
public function foobar() {}
}

class FoobarController
{
public function __invoke() {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,18 @@ public void testComponentNameTagNavigation() {
PlatformPatterns.psiElement(PhpClass.class)
);
}

public void testFoo() {
assertNavigationMatch(
TwigFileType.INSTANCE,
"{{ path('App\\\\Controller\\\\Foobar<caret>Controller') }}",
PlatformPatterns.psiElement(Method.class)
);

assertNavigationMatch(
TwigFileType.INSTANCE,
"{{ path('App\\\\Controller\\\\FooCo<caret>ntroller::foobar') }}",
PlatformPatterns.psiElement(Method.class)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ enum FooEnum
case BAR1;
}
}


namespace App\Controller
{
class FooController
{
public function foobar() {}
}

class FoobarController
{
public function __invoke() {}
}
}
Loading