Skip to content

Complete hash keys and values from the array shape of a Twig function's PHP parameter聽#108

Description

@Kocal

Hi 馃憢馃徎

Need

Many Twig functions take a hash of options, like button({ size: 'md' }). The PHP method behind the function often documents the accepted keys and values with a PHPDoc array shape. Language Tools could use that shape to complete the hash's keys and each key's literal values.

This is what css() needs in symfony/ux-css, proposed in symfony/ux#3933: it generates config/reference_css.php with a CssStyles shape (about 800 keys), and css() declares @param CssStyles|null $styles. But the need is generic: it applies to any function that takes a hash of options.

Current behavior

With Language Tools 0.23.0 and runtime indexing enabled, completion inside such a hash only offers the app global variable, for keys and for string values alike. Route names complete in the same template, so runtime indexing works.

Reproduction

// src/Twig/ButtonExtension.php
namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * @psalm-type ButtonOptions = array{size?: 'sm'|'md'|'lg', variant?: 'primary'|'secondary'|string, icon?: IconOptions}
 * @psalm-type IconOptions = array{name?: string, position?: 'start'|'end'}
 */
final class ButtonExtension extends AbstractExtension
{
    public function getFunctions(): array
    {
        return [
            new TwigFunction('button_direct', $this->direct(...)),
            new TwigFunction('button_alias', $this->alias(...)),
            new TwigFunction('button_imported', [ButtonRuntime::class, 'render']),
        ];
    }

    /**
     * @param array{size?: 'sm'|'md'|'lg', variant?: 'primary'|'secondary'} $options
     */
    public function direct(array $options): string { return ''; }

    /**
     * @param ButtonOptions $options
     */
    public function alias(array $options): string { return ''; }
}
// src/Twig/ButtonRuntime.php
namespace App\Twig;

use Twig\Extension\RuntimeExtensionInterface;

/**
 * @psalm-import-type ButtonOptions from ButtonExtension
 */
final class ButtonRuntime implements RuntimeExtensionInterface
{
    /**
     * @param ButtonOptions $options
     */
    public function render(array $options): string { return ''; }
}
{# templates/probe.html.twig, | marks the cursor #}
{{ button_direct({ | }) }}
{{ button_direct({ size: '|' }) }}
{{ button_alias({ | }) }}
{{ button_alias({ icon: { | } }) }}
{{ button_imported({ variant: '|' }) }}

Expected behavior

Line Cursor Expected items
1 key of the hash size, variant
2 value of size sm, md, lg
3 key, local alias size, variant, icon
4 key of a nested hash name, position
5 value, imported alias primary, secondary

The rules behind these cases:

  1. The PHP parameter's type is an array shape, written directly or through an alias declared with @psalm-type or @phpstan-type, possibly imported from another class with @psalm-import-type or @phpstan-import-type.
  2. In a hash at that argument's position, the shape's keys are completed, optional ones included.
  3. In a string value, the literal members of the key's type are completed. Other members, like string or int, are ignored.
  4. In a nested hash, the key's type is resolved, following aliases, and its keys are completed.
  5. The PHP method is found for the usual callable forms: $this->method(...), [$this, 'method'], and a runtime [Runtime::class, 'method'].

Constraints

  • The shape can be large: the one symfony/ux-css generates is about 125 KB with more than 800 keys, so parsing it once per file change matters more than parsing it fast.
  • Language Tools has no PHPDoc parser in its dependencies today (phpstan/phpdoc-parser is not required). How to read the docblock is your call.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions