• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / filament-user-attributes / 16222170016

11 Jul 2025 02:13PM UTC coverage: 74.446%. Remained the same
16222170016

push

github

luttje
.

1311 of 1761 relevant lines covered (74.45%)

30.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.68
/src/CodeGeneration/CodeEditor.php
1
<?php
2

3
namespace Luttje\FilamentUserAttributes\CodeGeneration;
4

5
use PhpParser\NodeTraverser;
6
use PhpParser\PrettyPrinter;
7
use PhpParser\NodeVisitor;
8
use PhpParser\ParserFactory;
9

10
/**
11
 * @internal
12
 */
13
final class CodeEditor
14
{
15
    protected static $recentBackupPaths = [];
16

17
    public static function make()
36✔
18
    {
19
        return new static();
36✔
20
    }
21

22
    public static function clearRecentBackupPaths()
4✔
23
    {
24
        static::$recentBackupPaths = [];
4✔
25
    }
26

27
    public static function getRecentBackupPaths()
8✔
28
    {
29
        return static::$recentBackupPaths;
8✔
30
    }
31

32
    public function editFileWithBackup($path, $callback)
8✔
33
    {
34
        $transaction = new CodeEditTransaction($path);
8✔
35
        $transaction->edit($callback);
8✔
36

37
        $backupPath = $transaction->getBackupFilePath();
8✔
38

39
        if ($backupPath) {
8✔
40
            self::$recentBackupPaths[$path] = $backupPath;
8✔
41
        }
42

43
        return $transaction;
8✔
44
    }
45

46
    public function addTrait($code, $trait)
8✔
47
    {
48
        return self::modifyCode(
8✔
49
            $code,
8✔
50
            self::fullyQualifyClass($trait),
8✔
51
            TraitInserter::class
8✔
52
        );
8✔
53
    }
54

55
    public function addInterface($code, $interface)
8✔
56
    {
57
        return self::modifyCode(
8✔
58
            $code,
8✔
59
            self::fullyQualifyClass($interface),
8✔
60
            InterfaceInserter::class
8✔
61
        );
8✔
62
    }
63

64
    public function addMethod($code, $methodName, ?\Closure $builder = null)
4✔
65
    {
66
        return self::modifyCode(
4✔
67
            $code,
4✔
68
            $methodName,
4✔
69
            MethodInserter::class,
4✔
70
            $builder
4✔
71
        );
4✔
72
    }
73

74
    public function modifyMethod($code, $methodName, ?\Closure $builder = null)
16✔
75
    {
76
        return self::modifyCode(
16✔
77
            $code,
16✔
78
            $methodName,
16✔
79
            MethodModifier::class,
16✔
80
            $builder
16✔
81
        );
16✔
82
    }
83

84
    private static function traverseUntil($ast, $callback)
12✔
85
    {
86
        $traverser = new NodeTraverser();
12✔
87
        $visitor = new NodeVisitor\FirstFindingVisitor($callback);
12✔
88
        $traverser->addVisitor($visitor);
12✔
89
        $traverser->traverse($ast);
12✔
90

91
        return $visitor->getFoundNode();
12✔
92
    }
93

94
    private static function traverseUntilUsing($ast, $symbolFilter)
24✔
95
    {
96
        $traverser = new NodeTraverser();
24✔
97
        $visitor = new UsingCollector($symbolFilter);
24✔
98
        $traverser->addVisitor($visitor);
24✔
99
        $traverser->traverse($ast);
24✔
100

101
        return $visitor->getFoundNode();
24✔
102
    }
103

104
    private static function parseAndTraverseUntilUsing($code, $symbolFilter)
24✔
105
    {
106
        $parser = (new ParserFactory())->createForNewestSupportedVersion();
24✔
107
        $ast = $parser->parse($code);
24✔
108

109
        return self::traverseUntilUsing($ast, $symbolFilter);
24✔
110
    }
111

112
    public static function usesTrait($code, $trait)
24✔
113
    {
114
        $trait = self::fullyQualifyClass($trait);
24✔
115
        $node = self::parseAndTraverseUntilUsing($code, $trait);
24✔
116

117
        return $node !== null;
24✔
118
    }
119

120
    public static function implementsInterface($code, $interface)
24✔
121
    {
122
        $interface = self::fullyQualifyClass($interface);
24✔
123
        $node = self::parseAndTraverseUntilUsing($code, $interface);
24✔
124

125
        return $node !== null;
24✔
126
    }
127

128
    public static function fullyQualifyClass($class)
44✔
129
    {
130
        if (strpos($class, '\\') === 0) {
44✔
131
            return $class;
×
132
        }
133

134
        return '\\' . $class;
44✔
135
    }
136

137
    public static function astFromTemplate($template)
×
138
    {
139
        $parser = (new ParserFactory())->createForNewestSupportedVersion();
×
140
        return $parser->parse($template);
×
141
    }
142

143
    private static function modifyCode($code, $nodeName, $modifierClass, ?\Closure $builder = null)
36✔
144
    {
145
        // Create a traverser to clone the AST so we can keep the original formatting
146
        $traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
36✔
147
        $parser = (new ParserFactory())->createForNewestSupportedVersion();
36✔
148

149
        $ast = $parser->parse($code);
36✔
150
        $origTokens = $parser->getTokens();
36✔
151

152
        $modifiedAst = $traverser->traverse($ast);
36✔
153

154
        // Create a new traverser to modify the AST
155
        $traverser = new NodeTraverser(new $modifierClass($nodeName, $builder));
36✔
156

157
        $modifiedAst = $traverser->traverse($modifiedAst);
36✔
158

159
        // Preserve most formatting
160
        $prettyPrinter = new PrettyPrinter\Standard();
36✔
161
        return $prettyPrinter->printFormatPreserving($modifiedAst, $ast, $origTokens);
36✔
162
    }
163

164
    /**
165
     * Recrusively traverse the statements and find the variable on this method is called.
166
     *
167
     * @param \PhpParser\Node $node A method or variable
168
     */
169
    private static function getOriginalVariableName($node)
12✔
170
    {
171
        if ($node instanceof \PhpParser\Node\Expr\MethodCall) {
12✔
172
            return self::getOriginalVariableName($node->var);
12✔
173
        } elseif ($node instanceof \PhpParser\Node\Expr\Variable) {
12✔
174
            return $node->name;
12✔
175
        }
176

177
        return false;
×
178
    }
179

180
    /**
181
     * Traverse the statements and find the variable on which the given method is called.
182
     */
183
    public static function findCall($stmts, $variableName, $methodName)
12✔
184
    {
185
        return self::traverseUntil($stmts, function ($node) use ($variableName, $methodName) {
12✔
186
            if (!($node instanceof \PhpParser\Node\Expr\MethodCall)) {
12✔
187
                return false;
12✔
188
            }
189

190
            $varName = self::getOriginalVariableName($node->var);
12✔
191

192
            return $varName === $variableName
12✔
193
                && $node->name->name === $methodName;
12✔
194
        });
12✔
195
    }
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc