Repository navigation
feat: Twig templates and controller-to-template links - #33
Merged
Merged
Conversation
Twig templates were in no FileType set, so they were silently skipped and the view layer of a Symfony, Drupal or Craft project was missing from the graph. Regex extractor in the shape of extractors/blade.py: includes, embeds, extends, imports_macro, uses_template, defines_block, attaches_library. References to a real file get an edge stamped with target_file and no stub node; Drupal SDC references resolve through <provider>.info.yml. Comments are blanked before scanning so docblock usage examples are not read as dependencies. Cherry-picked from Graphify-Labs#2240 (c61df7c), Twig part only: the SCSS extractor of that PR is a separate concern and is left out. Refs #19
…iguation When two files share a stem (a component's card.twig next to another extracted card.* file), _disambiguate_colliding_node_ids salts their ids apart, and an include edge from a third template no longer found its target. Mirror the Graphify-Labs#1475 C/ObjC header fix: repoint template-composition edges to the .twig variant, and honor the target_file stamp for those relations. Cherry-picked from Graphify-Labs#2240 (234579d), Twig part only. Refs #19
Twig names are relative to the loader root, never to the referring file, so a same-named file next to the referrer, or a nested templates/ dir, no longer wins. A Symfony @Namespace/ is a bundle or a configured path and stays a stub instead of being matched against the app templates; Drupal's @extension/ resolves under that extension, as core FilesystemLoader does. Refs #19 Co-Authored-By: Claude <noreply@anthropic.com>
A literal template name passed to render(), renderView(), renderBlock(), renderBlockView() or #[Template(...)] now yields a `renders` edge from the method to the template file node, resolved against the app's templates/ dir. A name built at runtime, or one with no file on disk, yields no edge. The mechanism lives in extractors/php_views.py so other template engines can plug their resolver into it. AST cache schema bumped so cached PHP extractions pick the new edges up. Refs #19 Co-Authored-By: Claude <noreply@anthropic.com>
Refs #19 Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for the pull request, @alexwrite. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #19
Problem
.twigfiles were in no FileType set: 0 nodes for the 130 templates of project B (Symfony 8.1), so theRoute -> Controller -> ... -> viewchain stopped at the controller and nothing said which pages a template change touches.Implementation
feat(extract): add a Twig template extractorfix(extract): keep template composition edges alive through id disambiguationfix(twig): resolve names against the loader root onlyfeat(php): link controllers to the Twig template they renderrendersedge, shared mechanism inextractors/php_views.pydocs(changelog)Why Graphify-Labs#2240 and not Graphify-Labs#2001 (both upstream, open), measured on project B with the same base:
.twigfile nodesextendsto the real fileincludeto the real fileGraphify-Labs#2001 mints a node with the same id as the target file node, so
_disambiguate_colliding_node_idssalts both apart and every link lands on a copy. Graphify-Labs#2240 stampstarget_fileon the edge and mints no node (the Graphify-Labs#2195 pattern).Changes on top of Graphify-Labs#2240, all fail-closed:
templates/dir, else the nearesttemplates/child walking up). feat(extract): add Twig and SCSS extractors Graphify-Labs/graphify#2240 tried the referring file's own directory first, which Twig never does; a same-named sibling won.@Namespace/x(Symfony bundle or configured path) stays a stub. feat(extract): add Twig and SCSS extractors Graphify-Labs/graphify#2240 stripped the namespace and matched the remainder against the app's templates. Drupal@extension/xresolves to<extension>/templates/xthen<extension>/x, as Drupal coreFilesystemLoaderdoes; a foreign extension's name no longer self-loops onto the local override.rendersedge from the PHP method to the template file for a literal name inrender(),renderView(),renderBlock(),renderBlockView()(any receiver, name ending in.twig) and#[Template('...')]/#[Template(template: '...')]. No edge for a concatenated or interpolated name, or a name with no file.extractors/php_views.pyholds the detection and an engine -> resolver table, so the Blade PR plugs into it instead of adding a second mechanism.rendersjoins_TEMPLATE_TARGET_RELATIONSand the engine's cross-file edge allowlist._AST_CACHE_SCHEMA8 -> 9: cached PHP extractions would otherwise never get the new edges.Twig name semantics: https://symfony.com/doc/current/templates.html ("the template path is the relative file path from
templates/"; bundle namespaces). Drupal:core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php(addPath($path . '/templates', $name)thenaddPath($path, $name)).Verification
Commands run in a dedicated worktree on this branch:
uv run pytest tests/ -q:7550 passed, 14 skipped(baseorigin/main: 7528 passed, 14 skipped).uv run ruff check .:All checks passed!uv run pyrighton every touched module: 199 errors on the branch, 199 onorigin/main, none new (per-message differential).tests/test_twig.py(sibling file, nestedtemplates/, bundle namespace, foreign Drupal namespace), the PHP-under-a-templates/-dir test, andtest_symfony_render_and_template_attribute_link_to_the_template_file(empty set without the engine wiring).graphify extract --code-only): 130/130 template nodes, 79/79extendsand 22/22includeon real files (1 stub:form_div_layout.html.twig, from the vendor bridge), 3 dynamic includes with no edge, 44rendersedges. 44 is the count of unique (file, method, template) for the 51 literal render calls, checked with an independent script.Limitations
<twig:Name>,component()) are not linked.twig.default_pathandtwig.pathsfromtwig.yamlare not read: the defaulttemplates/is assumed.TemplatedEmail::htmlTemplate()/textTemplate()are not followed.