Skip to content

fix(blade): resolve views to their file and link controllers to them - #40

Merged
alexwrite merged 7 commits into
mainfrom
fix/blade-view-resolution
Oct 11, 2026
Merged

alexwrite merged 7 commits into
mainfrom
fix/blade-view-resolution

Conversation

@alexwrite

Copy link
Copy Markdown
Owner

Closes #4

Stacked on #33. The controller link reuses the extractors/php_views.py mechanism introduced there (one detector, an engine -> resolver table), so this branch starts from feat/twig-templates. Review only the last two commits (fix(blade): ..., docs(changelog): Blade ...); merge #33 first.

Problem

On ClientXCMS (Laravel 12, 350 Blade views):

  • @extends('layouts.app') / @include('partials.nav') pointed at a stub node layouts/app, never at resources/views/layouts/app.blade.php: 155 extends and 626 includes edges, all on stubs.
  • view('admin.invoices.show') in a controller created no edge, so nothing said which controller renders a view.

Implementation

  • blade.py: resolve_blade_name() maps a name the way Laravel's FileViewFinder does (str_replace('.', '/', $name), extensions blade.php then php, laravel/framework v12.69.2 FileViewFinder.php:43 and :149) under the default view path, the nearest resources/views above the file. A resolved view gets an edge stamped with target_file and no stub node (the Absolute-path node ids on 0.9.25 for in-root relative import targets (residual of #1899 Variant B; ghost duplicates + dangling endpoints) Graphify-Labs/graphify#2195 pattern, as Twig). A namespaced (pkg::view), dynamic or missing name keeps the old stub. Repeated includes of one view are one edge.
  • php_views.py: view('...'), View::make('...') (short scope name View) and response()->view('...') with a literal first argument (or view: named argument) return ("blade", name); render_edge() resolves through resolve_blade_name. No second mechanism.
  • _AST_CACHE_SCHEMA 9 -> 10.

Verification

  • uv run pytest tests/ -q: 7552 passed, 14 skipped.
  • uv run ruff check .: All checks passed!. uv run pyright graphify/extractors/blade.py graphify/extractors/php_views.py: 0 errors.
  • Both new tests in tests/test_blade_extractor.py were seen failing before the fix (set() == {...}); the existing stub test still passes.
  • ClientXCMS, graphify extract --code-only, origin/main -> this branch:
main branch
extends on the view file 0 120 (35 stubs left)
includes on the view file 0 469 (184 stubs left)
renders controller -> view 0 59
nodes 11 684 11 104

Checked with an independent script: 120/120 and 469/469 resolvable (file, view) pairs are linked to the right file, including the 23 that land on the salted ids of mass-actions/ vs mass_actions/ (same make_id); 59/60 literal resolvable view() calls, the 60th being commented-out code. The stubs left are views present only in the theme folder, added at runtime by View::addLocation, and pkg::view names.

Limitations

amslezak and others added 7 commits October 11, 2026 13:21
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>
@extends('layouts.app') and @include('partials.nav') used to mint a stub
node `layouts/app` instead of reaching resources/views/layouts/app.blade.php.
They now resolve the way Laravel's FileViewFinder does (dots to slashes,
blade.php then php, under the default resources/views path) and point at the
file's own node; a namespaced, dynamic or missing view keeps its stub.

view('...'), View::make('...') and response()->view('...') with a literal
name now get a `renders` edge from the method to the view file, through the
same php_views mechanism as the Twig render calls. AST cache schema 10.

Refs #4

Co-Authored-By: Claude <noreply@anthropic.com>
Refs #4

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

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.

@alexwrite
alexwrite merged commit e357781 into main Oct 11, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blade : résoudre les vues vers le fichier réel et relier les contrôleurs

2 participants