3434import fr .adrienbrault .idea .symfony2plugin .stubs .indexes .UxTemplateStubIndex ;
3535import fr .adrienbrault .idea .symfony2plugin .stubs .util .IndexUtil ;
3636import fr .adrienbrault .idea .symfony2plugin .templating .path .TwigPath ;
37+ import fr .adrienbrault .idea .symfony2plugin .templating .path .UxComponentFactoryParser ;
3738import fr .adrienbrault .idea .symfony2plugin .templating .path .UxComponentTemplateFinderParser ;
3839import fr .adrienbrault .idea .symfony2plugin .templating .util .TwigTypeResolveUtil ;
3940import fr .adrienbrault .idea .symfony2plugin .templating .util .TwigUtil ;
41+ import fr .adrienbrault .idea .symfony2plugin .util .dict .CompiledTwigComponent ;
4042import fr .adrienbrault .idea .symfony2plugin .util .service .ServiceXmlParserFactory ;
4143import fr .adrienbrault .idea .symfony2plugin .util .dict .TwigComponentNamespace ;
4244import kotlin .Pair ;
@@ -140,8 +142,38 @@ public static void visitComponentsForIndex(@NotNull PhpFile phpFile, @NotNull Co
140142 }
141143 }
142144
145+ /**
146+ * Returns declared template names for a component class.
147+ *
148+ * Examples:
149+ * - \App\Twig\Components\Alert -> components/Alert.html.twig
150+ * - \App\Twig\Components\ShopCard -> components/shop/Card.html.twig from the compiled container
151+ */
143152 public static Collection <String > getComponentTemplatesForPhpClass (@ NotNull PhpClass phpClass ) {
144153 Set <String > templates = new HashSet <>();
154+ String fqn = phpClass .getFQN ();
155+
156+ UxComponentFactoryParser compiledComponentParser = getCompiledTwigComponentFactoryParser (phpClass .getProject ());
157+ Map <String , CompiledTwigComponent > compiledComponents = getCompiledTwigComponents (compiledComponentParser );
158+ for (CompiledTwigComponent component : compiledComponents .values ()) {
159+ if (fqn .equals (component .phpClass ()) && component .template () != null ) {
160+ templates .add (component .template ());
161+ }
162+ }
163+
164+ if (templates .isEmpty ()) {
165+ String componentName = compiledComponentParser != null ? compiledComponentParser .getClassMap ().get (fqn ) : null ;
166+ if (componentName != null ) {
167+ CompiledTwigComponent component = compiledComponents .get (componentName );
168+ if (component != null && component .template () != null ) {
169+ templates .add (component .template ());
170+ }
171+ }
172+ }
173+
174+ if (!templates .isEmpty ()) {
175+ return templates ;
176+ }
145177
146178 Set <String > names = new HashSet <>();
147179
@@ -159,7 +191,6 @@ public static Collection<String> getComponentTemplatesForPhpClass(@NotNull PhpCl
159191 return templates ;
160192 }
161193
162- String fqn = phpClass .getFQN ();
163194 for (TwigComponentNamespace twigComponentNamespace : getNamespaces (phpClass .getProject ())) {
164195 String componentNamespace = "\\ " + StringUtils .strip (twigComponentNamespace .namespace (), "\\ " ) + "\\ " ;
165196 if (!fqn .startsWith (componentNamespace )) {
@@ -262,7 +293,7 @@ public static Collection<TwigComponent> getAllComponentNames(@NotNull Project pr
262293 : addNamePrefix (value .phpClass ().substring (namespace1 .length ()).replace ("\\ " , ":" ), namespace .namePrefix ());
263294
264295 if (!name .isBlank ()) {
265- names .put (name , new TwigComponent (name , value .phpClass (), namespace ));
296+ names .put (name , new TwigComponent (name , value .phpClass (), namespace , value . template (), null ));
266297 }
267298
268299 break ;
@@ -271,6 +302,10 @@ public static Collection<TwigComponent> getAllComponentNames(@NotNull Project pr
271302 }
272303 }
273304
305+ for (CompiledTwigComponent component : getCompiledTwigComponents (project ).values ()) {
306+ mergeCompiledComponent (names , component );
307+ }
308+
274309 for (String valueValue : getAnonymousTemplateDirectories (project )) {
275310 String namespace = null ;
276311 if (valueValue .startsWith ("@" )) {
@@ -367,7 +402,7 @@ public boolean visitFile(@NotNull VirtualFile file) {
367402 String componentName = getAnonymousComponentNameFromRelativePath (relativePath );
368403 if (componentName != null ) {
369404 String name = StringUtils .isBlank (namePrefix ) ? componentName : namePrefix + ":" + componentName ;
370- names .putIfAbsent (name , new TwigComponent (name , null , null ));
405+ names .putIfAbsent (name , new TwigComponent (name , null , null , null , null ));
371406 }
372407
373408 return super .visitFile (file );
@@ -393,6 +428,13 @@ public static Set<PhpClass> getTwigComponentPhpClasses(@NotNull Project project,
393428 return phpClasses ;
394429 }
395430
431+ /**
432+ * Resolves a component name to concrete template files.
433+ *
434+ * Examples:
435+ * - Alert -> templates/components/Alert.html.twig
436+ * - Shop:Card -> templates/components/Shop/Card.html.twig
437+ */
396438 public static Collection <PsiFile > getComponentTemplates (@ NotNull Project project , @ NotNull String component ) {
397439 Collection <VirtualFile > virtualFiles = new LinkedHashSet <>();
398440
@@ -401,6 +443,14 @@ public static Collection<PsiFile> getComponentTemplates(@NotNull Project project
401443 continue ;
402444 }
403445
446+ if (entry .template () != null && addTemplateFilesWithFallback (project , entry .template (), virtualFiles )) {
447+ continue ;
448+ }
449+
450+ if (entry .templateFromMethod () != null ) {
451+ continue ;
452+ }
453+
404454 if (entry .twigComponentNamespace != null ) {
405455 String componentTemplateName = removeNamePrefix (component , entry .twigComponentNamespace .namePrefix ());
406456 if (componentTemplateName != null ) {
@@ -528,7 +578,15 @@ public static boolean hasComponentUsages(@NotNull TwigFile twigFile) {
528578 return false ;
529579 }
530580
581+ /**
582+ * Adds matching template files for a Symfony template name.
583+ *
584+ * Examples:
585+ * - components/Alert.html.twig resolved by Twig namespaces
586+ * - templates/components/Alert.html.twig via project fallback
587+ */
531588 private static boolean addTemplateFilesWithFallback (@ NotNull Project project , @ NotNull String templateName , @ NotNull Collection <VirtualFile > virtualFiles ) {
589+ // Use Twig's resolver first, including configured namespaces like "@Admin/components/Card.html.twig".
532590 Collection <VirtualFile > files = TwigUtil .getTemplateFiles (project , templateName );
533591 if (!files .isEmpty ()) {
534592 virtualFiles .addAll (files );
@@ -540,6 +598,7 @@ private static boolean addTemplateFilesWithFallback(@NotNull Project project, @N
540598 return false ;
541599 }
542600
601+ // Compiled UX metadata stores names relative to templates/, for example "components/Alert.html.twig".
543602 VirtualFile templatesDir = VfsUtil .findRelativeFile (baseDir , "templates" );
544603 if (templatesDir != null ) {
545604 VirtualFile virtualFile = VfsUtil .findRelativeFile (templatesDir , templateName .split ("/" ));
@@ -549,15 +608,6 @@ private static boolean addTemplateFilesWithFallback(@NotNull Project project, @N
549608 }
550609 }
551610
552- VirtualFile appViewsDir = VfsUtil .findRelativeFile (baseDir , "app" , "Resources" , "views" );
553- if (appViewsDir != null ) {
554- VirtualFile virtualFile = VfsUtil .findRelativeFile (appViewsDir , templateName .split ("/" ));
555- if (virtualFile != null ) {
556- virtualFiles .add (virtualFile );
557- return true ;
558- }
559- }
560-
561611 return false ;
562612 }
563613
@@ -570,7 +620,9 @@ public static Collection<PhpClass> getComponentClassesForTemplateFile(@NotNull P
570620 Collections .unmodifiableCollection (getComponentClassFqnsForTemplateFileInner (project , psiFile )),
571621 psiFile ,
572622 FileIndexCaches .getModificationTrackerForIndexId (project , UxTemplateStubIndex .KEY ),
573- FileIndexCaches .getModificationTrackerForIndexId (project , ConfigStubIndex .KEY )
623+ FileIndexCaches .getModificationTrackerForIndexId (project , ConfigStubIndex .KEY ),
624+ SymfonyVarDirectoryWatcherKt .getSymfonyVarDirectoryWatcher (project )
625+ .getModificationTracker (SymfonyVarDirectoryWatcher .Scope .CONTAINER )
574626 )
575627 );
576628
@@ -598,6 +650,12 @@ private static Collection<String> getComponentClassFqnsForTemplateFileInner(@Not
598650 }
599651 }
600652
653+ for (CompiledTwigComponent component : getCompiledTwigComponents (project ).values ()) {
654+ if (component .phpClass () != null && template .equals (component .template ())) {
655+ phpClassFqnsTemplateMatch .add (component .phpClass ());
656+ }
657+ }
658+
601659 if (!phpClassFqnsTemplateMatch .isEmpty ()) {
602660 phpClassFqns .addAll (phpClassFqnsTemplateMatch );
603661 break ;
@@ -620,6 +678,54 @@ private static Collection<String> getComponentClassFqnsForTemplateFileInner(@Not
620678 return phpClassFqns ;
621679 }
622680
681+ @ Nullable
682+ private static UxComponentFactoryParser getCompiledTwigComponentFactoryParser (@ NotNull Project project ) {
683+ return ServiceXmlParserFactory .getInstance (project , UxComponentFactoryParser .class );
684+ }
685+
686+ @ NotNull
687+ private static Map <String , CompiledTwigComponent > getCompiledTwigComponents (@ Nullable UxComponentFactoryParser parser ) {
688+ return parser != null ? parser .getComponents () : Collections .emptyMap ();
689+ }
690+
691+ @ NotNull
692+ private static Map <String , CompiledTwigComponent > getCompiledTwigComponents (@ NotNull Project project ) {
693+ return getCompiledTwigComponents (getCompiledTwigComponentFactoryParser (project ));
694+ }
695+
696+ private static void mergeCompiledComponent (
697+ @ NotNull Map <String , TwigComponent > components ,
698+ @ NotNull CompiledTwigComponent compiledComponent
699+ ) {
700+ TwigComponent existing = components .get (compiledComponent .name ());
701+
702+ if (existing == null ) {
703+ components .put (compiledComponent .name (), new TwigComponent (
704+ compiledComponent .name (),
705+ compiledComponent .phpClass (),
706+ null ,
707+ compiledComponent .template (),
708+ compiledComponent .templateFromMethod ()
709+ ));
710+ return ;
711+ }
712+
713+ components .put (compiledComponent .name (), new TwigComponent (
714+ compiledComponent .name (),
715+ compiledComponent .phpClass () != null ? compiledComponent .phpClass () : existing .phpClass (),
716+ existing .twigComponentNamespace (),
717+ compiledComponent .template () != null ? compiledComponent .template () : existing .template (),
718+ compiledComponent .templateFromMethod () != null ? compiledComponent .templateFromMethod () : existing .templateFromMethod ()
719+ ));
720+ }
721+
722+ private static Collection <UxComponent > getComponentsWithTemplates (@ NotNull Project project ) {
723+ return IndexUtil .getAllKeysForProject (UxTemplateStubIndex .KEY , project )
724+ .stream ().flatMap (key -> FileBasedIndex .getInstance ().getValues (UxTemplateStubIndex .KEY , key , GlobalSearchScope .allScope (project )).stream ())
725+ .filter (value -> value .template () != null )
726+ .collect (Collectors .toList ());
727+ }
728+
623729 public static Set <PhpClass > getTwigComponentAllTargets (@ NotNull Project project ) {
624730 Set <PhpClass > phpClasses = new HashSet <>();
625731
@@ -762,19 +868,18 @@ private static Collection<String> getExposeName(@NotNull PhpAttributesOwner phpA
762868 return names ;
763869 }
764870
765- private static Collection <UxComponent > getComponentsWithTemplates (@ NotNull Project project ) {
766- return IndexUtil .getAllKeysForProject (UxTemplateStubIndex .KEY , project )
767- .stream ().flatMap (key -> FileBasedIndex .getInstance ().getValues (UxTemplateStubIndex .KEY , key , GlobalSearchScope .allScope (project )).stream ())
768- .filter (value -> value .template () != null )
769- .collect (Collectors .toList ());
770- }
771-
772871 public enum TwigComponentType {
773872 LIVE_COMPONENT ,
774873 TWIG_COMPONENT ,
775874 }
776875
777876 public record TwigComponentIndex (@ Nullable String name , @ NotNull PhpClass phpClass , @ Nullable String template , @ NotNull TwigComponentType type ) {}
778877
779- public record TwigComponent (@ NotNull String name , @ Nullable String phpClass , @ Nullable TwigComponentNamespace twigComponentNamespace ) {}
878+ public record TwigComponent (
879+ @ NotNull String name ,
880+ @ Nullable String phpClass ,
881+ @ Nullable TwigComponentNamespace twigComponentNamespace ,
882+ @ Nullable String template ,
883+ @ Nullable String templateFromMethod
884+ ) {}
780885}
0 commit comments