Skip to content

[FEATURE] Add "Go to Definition" for ViewHelpers - #11

Open
chrissonntag wants to merge 4 commits into
FriendsOfTYPO3:mainfrom
chrissonntag:feature/viewhelper-go-to-definition
Open

[FEATURE] Add "Go to Definition" for ViewHelpers#11
chrissonntag wants to merge 4 commits into
FriendsOfTYPO3:mainfrom
chrissonntag:feature/viewhelper-go-to-definition

Conversation

@chrissonntag

@chrissonntag chrissonntag commented Aug 19, 2026

Copy link
Copy Markdown

This implements what I proposed in #10 : F12 or Ctrl+click on a ViewHelper opens the PHP class behind it — built-in, third-party and project ViewHelpers alike, in tag as well as in inline syntax.

Scope is deliberately narrow: a DefinitionProvider for fluid and html-fluid, nothing else — hover, completion and diagnostics would overlap with the generated html.customData and the analyzer. It is enabled by default and can be switched off with fluid.features.viewHelperDefinitions.

Global namespaces come from typo3 fluid:namespaces --json, so this needs TYPO3 v14.2, or EXT:fluid_companion in v12 and v13. Without it the feature stays off and points at the companion — there is no fallback that could resolve to a wrong class. Classes are located through the Composer autoload map, and the template's own xmlns and {namespace} declarations are added on top. Fluid: Reload ViewHelper Index re-reads the namespaces, for the case that they were registered by an event listener.

Tested on TYPO3 12.4, 13.4 and 14.3 DDEV projects and on a single extension repo with a tea-style .Build layout, which works without a database or settings.php. npm run lint passes.

The branch also contains an unrelated one-line bugfix: fluid.bin.useDdevIfAvailable was read from the wrong configuration key, so turning it off had no effect.

Resolves a ViewHelper in a Fluid template to the PHP class implementing it,
in tag as well as in inline syntax, and for project ViewHelpers just as well
as for built-in ones.

Resolution mirrors what Fluid does at runtime, without executing anything:
namespaces are collected from every package Configuration/Fluid/Namespaces.php,
from the legacy $GLOBALS registration in ext_localconf.php and from the xmlns
and {namespace} declarations of the template; the candidates of an alias are
tried in reverse order because Fluid resolves the last registered namespace
first; the tag name becomes a class name through ucfirst per dotted segment;
and the class is located through the Composer autoload map, vendor as well as
.Build/vendor. The resolved index is cached per workspace folder and dropped
when a namespace file, an ext_localconf.php or the autoload map changes.

Because no binary is involved, this also works in projects where live
template analysis is unavailable.

@s2b s2b left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we would move all php parts to the php side, especially since the required classes are already implemented, but let's start with using what's already available.

I've looked through the code, but didn't test it yet. As discussed, this would be a temporary solution, but it's certainly better than no solution. Thanks for the effort!

Comment thread client/src/viewHelpers.ts Outdated
The candidate list of typo3 and fluid binaries was assembled inline twice,
once for fluid:analyze and once for --version, so every further command had
to repeat the same block. Both now come from a shared module, which also
runs a command asynchronously and formats it for the log.

The candidate order is unchanged: user defined binaries first, then ddev,
then vendor/bin, bin and .Build/bin.
fluid.bin.useDdevIfAvailable was read as "useDdevIfAvailable" instead of
"bin.useDdevIfAvailable", so the configured value never arrived and binaries
were always run inside DDEV whenever a project had it set up. Turning the
setting off had no effect.
Global namespaces now come from typo3 fluid:namespaces --json instead of
being pattern matched out of Configuration/Fluid/Namespaces.php and
ext_localconf.php. They are therefore exactly the namespaces Fluid resolves,
in the order it resolves them, which decides whether an extension overrides
a core ViewHelper; the previous directory walk could pick a different winner.
Ignored and wildcard aliases are skipped, and classes are still located
through the Composer autoload map.

The command ships with TYPO3 v14.2 and is backported to v12 and v13 by
EXT:fluid_companion. Without it the feature stays off and points at the
companion, rather than guessing and resolving to a plausible but wrong class.
--no-interaction is mandatory, because a missing command otherwise makes
Symfony Console ask which command to run instead.

The namespaces are read once per workspace folder, asynchronously and shared
by concurrent lookups. The index is now also dropped when the system
configuration, the Composer lock or the package artifact changes, and the new
"Fluid: Reload ViewHelper Index" command covers namespaces registered by an
event listener, which no watcher can see.
@chrissonntag
chrissonntag force-pushed the feature/viewhelper-go-to-definition branch from 219b385 to 3bad19d Compare September 4, 2026 13:59
@chrissonntag

Copy link
Copy Markdown
Author

Ok, changes are in. Feel free to test it yourself and see whether the feature works for you, or let me know what else we could improve.

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.

2 participants