cg-code-graph 0.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cg_code_graph-0.10.1.dist-info/METADATA +678 -0
- cg_code_graph-0.10.1.dist-info/RECORD +174 -0
- cg_code_graph-0.10.1.dist-info/WHEEL +5 -0
- cg_code_graph-0.10.1.dist-info/entry_points.txt +3 -0
- cg_code_graph-0.10.1.dist-info/licenses/LICENSE +21 -0
- cg_code_graph-0.10.1.dist-info/top_level.txt +1 -0
- codegraph/__init__.py +2 -0
- codegraph/aitools.py +129 -0
- codegraph/apps.py +76 -0
- codegraph/blindspots.py +428 -0
- codegraph/bridges.py +1701 -0
- codegraph/cli.py +725 -0
- codegraph/concepts.py +362 -0
- codegraph/config.py +559 -0
- codegraph/core/__init__.py +0 -0
- codegraph/core/cache.py +375 -0
- codegraph/core/detect.py +80 -0
- codegraph/core/extractors.py +187 -0
- codegraph/core/fsutil.py +61 -0
- codegraph/core/generated.py +575 -0
- codegraph/core/model.py +174 -0
- codegraph/core/paths.py +175 -0
- codegraph/core/plugin.py +160 -0
- codegraph/core/store.py +80 -0
- codegraph/core/syntax_errors.py +132 -0
- codegraph/coverage.py +928 -0
- codegraph/doctor.py +453 -0
- codegraph/external.py +613 -0
- codegraph/indexer.py +336 -0
- codegraph/link.py +434 -0
- codegraph/lint_async.py +524 -0
- codegraph/mcp_server.py +1303 -0
- codegraph/parity.py +473 -0
- codegraph/parity_structure.py +307 -0
- codegraph/payload.py +321 -0
- codegraph/plans.py +1285 -0
- codegraph/platform_scan.py +643 -0
- codegraph/platforms.py +1369 -0
- codegraph/plugins/__init__.py +0 -0
- codegraph/plugins/cfamily/__init__.py +0 -0
- codegraph/plugins/cfamily/plugin.py +930 -0
- codegraph/plugins/cfamily/syntax.py +881 -0
- codegraph/plugins/dart/__init__.py +0 -0
- codegraph/plugins/dart/bridges.py +345 -0
- codegraph/plugins/dart/extractor/bin/extract.dart +717 -0
- codegraph/plugins/dart/extractor/pubspec.lock +149 -0
- codegraph/plugins/dart/extractor/pubspec.yaml +7 -0
- codegraph/plugins/dart/http.py +904 -0
- codegraph/plugins/dart/models.py +308 -0
- codegraph/plugins/dart/plugin.py +625 -0
- codegraph/plugins/dart/program.py +907 -0
- codegraph/plugins/django/__init__.py +0 -0
- codegraph/plugins/django/extras.py +378 -0
- codegraph/plugins/django/models.py +508 -0
- codegraph/plugins/django/plugin.py +728 -0
- codegraph/plugins/django/schemas.py +339 -0
- codegraph/plugins/django/shapes.py +216 -0
- codegraph/plugins/django/urls.py +603 -0
- codegraph/plugins/express/__init__.py +0 -0
- codegraph/plugins/express/plugin.py +428 -0
- codegraph/plugins/flutter/__init__.py +0 -0
- codegraph/plugins/flutter/plugin.py +538 -0
- codegraph/plugins/kotlin/__init__.py +0 -0
- codegraph/plugins/kotlin/exact.py +457 -0
- codegraph/plugins/kotlin/plugin.py +1961 -0
- codegraph/plugins/kotlin/reparse.py +234 -0
- codegraph/plugins/laravel/__init__.py +0 -0
- codegraph/plugins/laravel/broadcast.py +351 -0
- codegraph/plugins/laravel/plugin.py +863 -0
- codegraph/plugins/laravel/tests.py +262 -0
- codegraph/plugins/laravel/values.py +728 -0
- codegraph/plugins/native/__init__.py +0 -0
- codegraph/plugins/native/gates.py +286 -0
- codegraph/plugins/native/runner.py +183 -0
- codegraph/plugins/native/scipread.py +194 -0
- codegraph/plugins/native/ts.py +54 -0
- codegraph/plugins/nest/__init__.py +0 -0
- codegraph/plugins/nest/plugin.py +654 -0
- codegraph/plugins/nextjs/__init__.py +0 -0
- codegraph/plugins/nextjs/plugin.py +336 -0
- codegraph/plugins/nuxt/__init__.py +0 -0
- codegraph/plugins/nuxt/plugin.py +308 -0
- codegraph/plugins/php/__init__.py +0 -0
- codegraph/plugins/php/extractor/composer.json +5 -0
- codegraph/plugins/php/extractor/composer.lock +76 -0
- codegraph/plugins/php/extractor/extract.php +743 -0
- codegraph/plugins/php/gating.py +573 -0
- codegraph/plugins/php/plugin.py +668 -0
- codegraph/plugins/php/strings.py +197 -0
- codegraph/plugins/python/__init__.py +0 -0
- codegraph/plugins/python/aitools.py +664 -0
- codegraph/plugins/python/external.py +245 -0
- codegraph/plugins/python/fields.py +107 -0
- codegraph/plugins/python/plugin.py +1733 -0
- codegraph/plugins/python/refs.py +485 -0
- codegraph/plugins/python/roots.py +412 -0
- codegraph/plugins/python/socketio.py +210 -0
- codegraph/plugins/python/subproc.py +864 -0
- codegraph/plugins/python/tests.py +1040 -0
- codegraph/plugins/python/values.py +179 -0
- codegraph/plugins/pyweb/__init__.py +0 -0
- codegraph/plugins/pyweb/plugin.py +1334 -0
- codegraph/plugins/pyweb/values.py +68 -0
- codegraph/plugins/rust/__init__.py +0 -0
- codegraph/plugins/rust/cargo.py +226 -0
- codegraph/plugins/rust/plugin.py +980 -0
- codegraph/plugins/rust/syntax.py +678 -0
- codegraph/plugins/scip/__init__.py +0 -0
- codegraph/plugins/scip/importer.py +129 -0
- codegraph/plugins/scip/scip.proto +962 -0
- codegraph/plugins/scip/scip_pb2.py +97 -0
- codegraph/plugins/stubs/__init__.py +0 -0
- codegraph/plugins/stubs/plugins.py +38 -0
- codegraph/plugins/swift/__init__.py +0 -0
- codegraph/plugins/swift/baseurl.py +109 -0
- codegraph/plugins/swift/exact.py +415 -0
- codegraph/plugins/swift/indexstore.py +209 -0
- codegraph/plugins/swift/packages.py +174 -0
- codegraph/plugins/swift/plugin.py +2890 -0
- codegraph/plugins/ts/__init__.py +0 -0
- codegraph/plugins/ts/baseurl.py +185 -0
- codegraph/plugins/ts/extractor/extract.mjs +2652 -0
- codegraph/plugins/ts/extractor/fw.mjs +685 -0
- codegraph/plugins/ts/extractor/package-lock.json +205 -0
- codegraph/plugins/ts/extractor/package.json +9 -0
- codegraph/plugins/ts/plugin.py +480 -0
- codegraph/plugins/tsweb/__init__.py +0 -0
- codegraph/plugins/tsweb/common.py +290 -0
- codegraph/plugins/tsweb/data.py +276 -0
- codegraph/presets/__init__.py +146 -0
- codegraph/presets/c_cpp.yaml +9 -0
- codegraph/presets/common.yaml +66 -0
- codegraph/presets/dart.yaml +9 -0
- codegraph/presets/django-ninja.yaml +15 -0
- codegraph/presets/django.yaml +25 -0
- codegraph/presets/djangorestframework.yaml +17 -0
- codegraph/presets/express.yaml +17 -0
- codegraph/presets/kotlin.yaml +11 -0
- codegraph/presets/laravel.yaml +40 -0
- codegraph/presets/nest.yaml +11 -0
- codegraph/presets/nextjs.yaml +15 -0
- codegraph/presets/nuxt.yaml +9 -0
- codegraph/presets/php.yaml +5 -0
- codegraph/presets/python.yaml +10 -0
- codegraph/presets/rust.yaml +5 -0
- codegraph/presets/swift.yaml +10 -0
- codegraph/presets/typescript.yaml +13 -0
- codegraph/process_runs.py +328 -0
- codegraph/protocols/__init__.py +299 -0
- codegraph/protocols/builtin.py +67 -0
- codegraph/protocols/matchers.py +144 -0
- codegraph/protocols/view.py +334 -0
- codegraph/query.py +2089 -0
- codegraph/realtime.py +260 -0
- codegraph/roundtrip.py +346 -0
- codegraph/routes.py +442 -0
- codegraph/starters.py +218 -0
- codegraph/tests_index.py +117 -0
- codegraph/viz/__init__.py +0 -0
- codegraph/viz/graph.py +369 -0
- codegraph/viz/server.py +198 -0
- codegraph/viz/static/app.css +148 -0
- codegraph/viz/static/app.js +1082 -0
- codegraph/viz/static/index.html +81 -0
- codegraph/viz/static/layered.js +237 -0
- codegraph/viz/static/vendor/VERSIONS.txt +4 -0
- codegraph/viz/static/vendor/cose-base.js +3214 -0
- codegraph/viz/static/vendor/cytoscape-fcose.js +1549 -0
- codegraph/viz/static/vendor/cytoscape.min.js +31 -0
- codegraph/viz/static/vendor/layout-base.js +5230 -0
- codegraph/viz/tools/package-lock.json +303 -0
- codegraph/viz/tools/package.json +7 -0
- codegraph/viz/tools/shoot.mjs +165 -0
- codegraph/xcode.py +251 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* code-graph PHP fact extractor (deterministic, nikic/php-parser v5).
|
|
4
|
+
*
|
|
5
|
+
* Usage: php extract.php <project_root> <file-list.txt> > facts.jsonl
|
|
6
|
+
* Emits one JSON object per file: declarations (classes/methods/props/functions)
|
|
7
|
+
* and in-order "facts" (calls, news, property fetches, assignments, class refs)
|
|
8
|
+
* with symbolic receiver descriptors. Type resolution across files happens in
|
|
9
|
+
* the Python core; this script only resolves names (use statements -> FQCN).
|
|
10
|
+
*/
|
|
11
|
+
declare(strict_types=1);
|
|
12
|
+
require __DIR__ . '/vendor/autoload.php';
|
|
13
|
+
|
|
14
|
+
use PhpParser\Node;
|
|
15
|
+
use PhpParser\Node\Expr;
|
|
16
|
+
use PhpParser\Node\Stmt;
|
|
17
|
+
use PhpParser\Node\Scalar;
|
|
18
|
+
use PhpParser\NodeTraverser;
|
|
19
|
+
use PhpParser\NodeVisitor\NameResolver;
|
|
20
|
+
use PhpParser\ParserFactory;
|
|
21
|
+
|
|
22
|
+
ini_set('memory_limit', '2G');
|
|
23
|
+
$root = rtrim($argv[1], '/');
|
|
24
|
+
$files = array_filter(array_map('trim', file($argv[2])));
|
|
25
|
+
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
|
26
|
+
|
|
27
|
+
final class Ctx {
|
|
28
|
+
public ?string $class = null; // current class FQCN
|
|
29
|
+
public ?string $parent = null; // parent FQCN
|
|
30
|
+
public array $facts = []; // collected facts for current function-like
|
|
31
|
+
public array $closureStack = []; // enclosing call contexts for closures
|
|
32
|
+
public array $ranges = []; // spl_object_id(node) => [lo, hi) fact-index range of the subtree
|
|
33
|
+
public array $nodeFact = []; // spl_object_id(call node) => fact index
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function nameStr($n): ?string {
|
|
37
|
+
if ($n instanceof Node\Name) { return $n->toString(); }
|
|
38
|
+
if ($n instanceof Node\Identifier) { return $n->toString(); }
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveSpecial(?string $name, Ctx $c): ?string {
|
|
43
|
+
if ($name === null) return null;
|
|
44
|
+
$l = strtolower($name);
|
|
45
|
+
if ($l === 'self' || $l === 'static') return $c->class;
|
|
46
|
+
if ($l === 'parent') return $c->parent;
|
|
47
|
+
return ltrim($name, '\\');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function typeList($t, ?\PhpParser\NameContext $nc = null): array {
|
|
51
|
+
if ($t === null) return [];
|
|
52
|
+
if ($t instanceof Node\NullableType) return typeList($t->type, $nc);
|
|
53
|
+
if ($t instanceof Node\UnionType || $t instanceof Node\IntersectionType) {
|
|
54
|
+
$o = []; foreach ($t->types as $x) { $o = array_merge($o, typeList($x, $nc)); } return $o;
|
|
55
|
+
}
|
|
56
|
+
if ($t instanceof Node\Name) return [ltrim($t->toString(), '\\')];
|
|
57
|
+
if ($t instanceof Node\Identifier) return [$t->toString()];
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Resolve a docblock type string like "?Collection<int, Product>|Foo[]" into class names. */
|
|
62
|
+
function docTypes(string $s, \PhpParser\NameContext $nc): array {
|
|
63
|
+
$out = [];
|
|
64
|
+
$s = preg_replace('/<.*>/', '', $s);
|
|
65
|
+
foreach (preg_split('/[|&]/', $s) as $part) {
|
|
66
|
+
$part = trim($part, " ?()");
|
|
67
|
+
$part = preg_replace('/\[\]$/', '', $part);
|
|
68
|
+
if ($part === '' || !preg_match('/^\\\\?[A-Za-z_][A-Za-z0-9_\\\\]*$/', $part)) continue;
|
|
69
|
+
$lower = strtolower($part);
|
|
70
|
+
if (in_array($lower, ['int','string','bool','float','array','mixed','void','null','callable','iterable','object','false','true','self','static','$this','never','resource','list','non-empty-string','positive-int','class-string'], true)) {
|
|
71
|
+
if (in_array($lower, ['self','static','$this'], true)) $out[] = $lower;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
$out[] = ltrim($nc->getResolvedClassName(new Node\Name($part))->toString(), '\\');
|
|
75
|
+
}
|
|
76
|
+
return $out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function docText($node): ?string {
|
|
80
|
+
$d = $node->getDocComment();
|
|
81
|
+
return $d ? $d->getText() : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Symbolic descriptor of an expression (receiver / argument). */
|
|
85
|
+
function desc($e, Ctx $c, int $depth = 0) {
|
|
86
|
+
if ($e === null) return null;
|
|
87
|
+
if ($depth > 12) return ['k' => 'deep'];
|
|
88
|
+
if ($e instanceof Node\Arg) { $e = $e->value; }
|
|
89
|
+
if ($e instanceof Expr\Variable) {
|
|
90
|
+
if (is_string($e->name)) return $e->name === 'this' ? ['k' => 'this'] : ['k' => 'var', 'n' => $e->name];
|
|
91
|
+
return ['k' => 'dynvar'];
|
|
92
|
+
}
|
|
93
|
+
if ($e instanceof Scalar\String_) return ['k' => 'str', 'v' => $e->value];
|
|
94
|
+
if ($e instanceof Scalar\Int_) return ['k' => 'int', 'v' => $e->value];
|
|
95
|
+
if ($e instanceof Scalar\InterpolatedString) {
|
|
96
|
+
$parts = [];
|
|
97
|
+
foreach ($e->parts as $p) {
|
|
98
|
+
if ($p instanceof Node\InterpolatedStringPart) $parts[] = ['k' => 'str', 'v' => $p->value];
|
|
99
|
+
else $parts[] = desc($p, $c, $depth + 1);
|
|
100
|
+
}
|
|
101
|
+
return ['k' => 'interp', 'parts' => $parts];
|
|
102
|
+
}
|
|
103
|
+
if ($e instanceof Expr\BinaryOp\Concat) {
|
|
104
|
+
return ['k' => 'concat', 'l' => desc($e->left, $c, $depth + 1), 'r' => desc($e->right, $c, $depth + 1)];
|
|
105
|
+
}
|
|
106
|
+
if ($e instanceof Expr\PropertyFetch || $e instanceof Expr\NullsafePropertyFetch) {
|
|
107
|
+
return ['k' => 'prop', 'of' => desc($e->var, $c, $depth + 1), 'n' => nameStr($e->name)];
|
|
108
|
+
}
|
|
109
|
+
if ($e instanceof Expr\StaticPropertyFetch) {
|
|
110
|
+
return ['k' => 'sprop', 'class' => resolveSpecial(nameStr($e->class), $c), 'n' => nameStr($e->name)];
|
|
111
|
+
}
|
|
112
|
+
if ($e instanceof Expr\MethodCall || $e instanceof Expr\NullsafeMethodCall) {
|
|
113
|
+
return ['k' => 'mcall', 'of' => desc($e->var, $c, $depth + 1), 'm' => nameStr($e->name), 'args' => argsDesc($e->args, $c, $depth + 1)];
|
|
114
|
+
}
|
|
115
|
+
if ($e instanceof Expr\StaticCall) {
|
|
116
|
+
return ['k' => 'scall', 'class' => resolveSpecial(nameStr($e->class), $c), 'm' => nameStr($e->name), 'args' => argsDesc($e->args, $c, $depth + 1)];
|
|
117
|
+
}
|
|
118
|
+
if ($e instanceof Expr\New_) {
|
|
119
|
+
if ($e->class instanceof Stmt\Class_) return ['k' => 'new', 'class' => null];
|
|
120
|
+
return ['k' => 'new', 'class' => resolveSpecial(nameStr($e->class), $c)];
|
|
121
|
+
}
|
|
122
|
+
if ($e instanceof Expr\FuncCall) {
|
|
123
|
+
return ['k' => 'func', 'n' => nameStr($e->name), 'args' => argsDesc($e->args, $c, $depth + 1)];
|
|
124
|
+
}
|
|
125
|
+
if ($e instanceof Expr\ClassConstFetch) {
|
|
126
|
+
$cn = nameStr($e->name);
|
|
127
|
+
$cls = resolveSpecial(nameStr($e->class), $c);
|
|
128
|
+
if ($cn === 'class') return ['k' => 'classconst', 'class' => $cls];
|
|
129
|
+
return ['k' => 'const', 'class' => $cls, 'n' => $cn];
|
|
130
|
+
}
|
|
131
|
+
if ($e instanceof Expr\Array_) {
|
|
132
|
+
$items = [];
|
|
133
|
+
foreach ($e->items as $it) {
|
|
134
|
+
if ($it === null) continue;
|
|
135
|
+
$items[] = ['key' => $it->key ? desc($it->key, $c, $depth + 1) : null, 'v' => desc($it->value, $c, $depth + 1)];
|
|
136
|
+
if (count($items) > 60) break;
|
|
137
|
+
}
|
|
138
|
+
return ['k' => 'arr', 'items' => $items];
|
|
139
|
+
}
|
|
140
|
+
if ($e instanceof Expr\Ternary) {
|
|
141
|
+
return ['k' => 'alt', 'op' => $e->if === null ? 'elvis' : 'ternary', 'opts' => [desc($e->if ?? $e->cond, $c, $depth + 1), desc($e->else, $c, $depth + 1)]]; // see skel for conditions
|
|
142
|
+
}
|
|
143
|
+
if ($e instanceof Expr\BinaryOp\Coalesce) {
|
|
144
|
+
return ['k' => 'alt', 'op' => 'coalesce', 'opts' => [desc($e->left, $c, $depth + 1), desc($e->right, $c, $depth + 1)]];
|
|
145
|
+
}
|
|
146
|
+
if ($e instanceof Expr\Cast) return desc($e->expr, $c, $depth + 1);
|
|
147
|
+
if ($e instanceof Expr\Clone_) return desc($e->expr, $c, $depth + 1);
|
|
148
|
+
if ($e instanceof Expr\ArrayDimFetch) {
|
|
149
|
+
return ['k' => 'dim', 'of' => desc($e->var, $c, $depth + 1), 'key' => $e->dim === null ? null : desc($e->dim, $c, $depth + 1)];
|
|
150
|
+
}
|
|
151
|
+
if ($e instanceof Expr\Closure || $e instanceof Expr\ArrowFunction) return ['k' => 'closure'];
|
|
152
|
+
if ($e instanceof Expr\ConstFetch) return ['k' => 'cfetch', 'n' => nameStr($e->name)];
|
|
153
|
+
if ($e instanceof Expr\Match_) {
|
|
154
|
+
$o = []; foreach ($e->arms as $a) { $o[] = desc($a->body, $c, $depth + 1); } return ['k' => 'alt', 'op' => 'match', 'opts' => $o];
|
|
155
|
+
}
|
|
156
|
+
if ($e instanceof Expr\Assign) return desc($e->expr, $c, $depth + 1);
|
|
157
|
+
return ['k' => 'other', 't' => $e->getType()];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function argsDesc(array $args, Ctx $c, int $depth = 0): array {
|
|
161
|
+
$o = [];
|
|
162
|
+
foreach ($args as $i => $a) {
|
|
163
|
+
if (!($a instanceof Node\Arg)) { $o[] = ['k' => 'variadic']; continue; }
|
|
164
|
+
$d = desc($a->value, $c, $depth);
|
|
165
|
+
if ($a->name) $d['named'] = $a->name->toString();
|
|
166
|
+
$o[] = $d;
|
|
167
|
+
if ($i > 12) break;
|
|
168
|
+
}
|
|
169
|
+
return $o;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function addFact(Ctx $c, array $f, Node $n): void {
|
|
173
|
+
$f['line'] = $n->getStartLine();
|
|
174
|
+
if (($f['t'] ?? null) === 'call') {
|
|
175
|
+
$c->nodeFact[spl_object_id($n)] = count($c->facts);
|
|
176
|
+
if ($n->getEndLine() > $f['line']) $f['end'] = $n->getEndLine(); // multi-line calls: closure bodies lie inside
|
|
177
|
+
}
|
|
178
|
+
if ($c->closureStack) $f['ctx'] = end($c->closureStack);
|
|
179
|
+
$c->facts[] = $f;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Walk any node collecting facts; records the fact-index range of every subtree (for gating). */
|
|
183
|
+
function walk($node, Ctx $c, \PhpParser\NameContext $nc): void {
|
|
184
|
+
if (is_array($node)) { foreach ($node as $n) walk($n, $c, $nc); return; }
|
|
185
|
+
if (!($node instanceof Node)) return;
|
|
186
|
+
$lo = count($c->facts);
|
|
187
|
+
walkNode($node, $c, $nc);
|
|
188
|
+
$c->ranges[spl_object_id($node)] = [$lo, count($c->facts)];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function walkNode(Node $node, Ctx $c, \PhpParser\NameContext $nc): void {
|
|
192
|
+
// nested class-likes are handled at declaration level
|
|
193
|
+
if ($node instanceof Stmt\ClassLike || $node instanceof Stmt\Function_) return;
|
|
194
|
+
|
|
195
|
+
// inline @var docblocks
|
|
196
|
+
$doc = $node->getDocComment();
|
|
197
|
+
if ($doc && $node instanceof Stmt && preg_match_all('/@var\s+(\S+)\s+\$(\w+)/', $doc->getText(), $mm, PREG_SET_ORDER)) {
|
|
198
|
+
foreach ($mm as $m) addFact($c, ['t' => 'vartype', 'var' => $m[2], 'types' => docTypes($m[1], $nc), 'src' => 'doc'], $node);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// #88: property writes that are not a plain `$o->p = v` (those are 'fetch' write facts below). A separate fact
|
|
202
|
+
// type, so framework handlers reading 'fetch' facts (model attributes, relations) see exactly what they saw before.
|
|
203
|
+
$pw = null;
|
|
204
|
+
if ($node instanceof Expr\AssignOp && !($node instanceof Expr\AssignOp\Coalesce)) $pw = [$node->var, 'compound'];
|
|
205
|
+
elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PostInc || $node instanceof Expr\PreDec || $node instanceof Expr\PostDec) $pw = [$node->var, 'compound'];
|
|
206
|
+
elseif (($node instanceof Expr\Assign || $node instanceof Expr\AssignRef || $node instanceof Expr\AssignOp) && $node->var instanceof Expr\ArrayDimFetch) $pw = [$node->var, 'item'];
|
|
207
|
+
if ($pw) {
|
|
208
|
+
$tg = $pw[0];
|
|
209
|
+
while ($tg instanceof Expr\ArrayDimFetch) { $tg = $tg->var; if ($pw[1] === 'compound') $pw[1] = 'item'; }
|
|
210
|
+
if ($tg instanceof Expr\PropertyFetch || $tg instanceof Expr\NullsafePropertyFetch)
|
|
211
|
+
addFact($c, ['t' => 'pwrite', 'recv' => desc($tg->var, $c), 'prop' => nameStr($tg->name), 'via' => $pw[1]], $node);
|
|
212
|
+
}
|
|
213
|
+
if ($node instanceof Stmt\Unset_) {
|
|
214
|
+
foreach ($node->vars as $v) {
|
|
215
|
+
$via = 'unset';
|
|
216
|
+
while ($v instanceof Expr\ArrayDimFetch) { $v = $v->var; $via = 'item'; }
|
|
217
|
+
if ($v instanceof Expr\PropertyFetch || $v instanceof Expr\NullsafePropertyFetch)
|
|
218
|
+
addFact($c, ['t' => 'pwrite', 'recv' => desc($v->var, $c), 'prop' => nameStr($v->name), 'via' => $via], $node);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ($node instanceof Expr\Assign || $node instanceof Expr\AssignRef || $node instanceof Expr\AssignOp\Coalesce) {
|
|
223
|
+
$target = $node->var;
|
|
224
|
+
if ($target instanceof Expr\Variable && is_string($target->name)) {
|
|
225
|
+
addFact($c, ['t' => 'assign', 'var' => $target->name, 'expr' => desc($node->expr, $c)], $node);
|
|
226
|
+
} elseif ($target instanceof Expr\PropertyFetch || $target instanceof Expr\NullsafePropertyFetch) {
|
|
227
|
+
addFact($c, ['t' => 'fetch', 'recv' => desc($target->var, $c), 'prop' => nameStr($target->name), 'write' => true], $node);
|
|
228
|
+
walk($target->var, $c, $nc);
|
|
229
|
+
walk($node->expr, $c, $nc);
|
|
230
|
+
return;
|
|
231
|
+
} elseif ($target instanceof Expr\List_ || $target instanceof Expr\Array_) {
|
|
232
|
+
// destructuring: ignore types
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if ($node instanceof Expr\Instanceof_ && $node->expr instanceof Expr\Variable && is_string($node->expr->name) && $node->class instanceof Node\Name) {
|
|
236
|
+
addFact($c, ['t' => 'vartype', 'var' => $node->expr->name, 'types' => [ltrim($node->class->toString(), '\\')], 'src' => 'instanceof'], $node);
|
|
237
|
+
}
|
|
238
|
+
if ($node instanceof Stmt\Foreach_ && $node->valueVar instanceof Expr\Variable && is_string($node->valueVar->name)) {
|
|
239
|
+
addFact($c, ['t' => 'assign', 'var' => $node->valueVar->name, 'expr' => ['k' => 'elem', 'of' => desc($node->expr, $c)]], $node);
|
|
240
|
+
}
|
|
241
|
+
if ($node instanceof Stmt\Catch_ && $node->var && is_string($node->var->name)) {
|
|
242
|
+
addFact($c, ['t' => 'vartype', 'var' => $node->var->name, 'types' => array_map(fn($t) => ltrim($t->toString(), '\\'), $node->types), 'src' => 'catch'], $node);
|
|
243
|
+
}
|
|
244
|
+
if ($node instanceof Expr\MethodCall || $node instanceof Expr\NullsafeMethodCall) {
|
|
245
|
+
addFact($c, ['t' => 'call', 'kind' => 'method', 'recv' => desc($node->var, $c), 'm' => nameStr($node->name), 'args' => argsDesc($node->args, $c)], $node);
|
|
246
|
+
} elseif ($node instanceof Expr\StaticCall) {
|
|
247
|
+
addFact($c, ['t' => 'call', 'kind' => 'static', 'class' => resolveSpecial(nameStr($node->class), $c), 'm' => nameStr($node->name), 'args' => argsDesc($node->args, $c), 'parentcall' => (nameStr($node->class) === 'parent')], $node);
|
|
248
|
+
} elseif ($node instanceof Expr\FuncCall) {
|
|
249
|
+
addFact($c, ['t' => 'call', 'kind' => 'func', 'n' => nameStr($node->name), 'args' => argsDesc($node->args, $c)], $node);
|
|
250
|
+
} elseif ($node instanceof Expr\New_) {
|
|
251
|
+
$cls = ($node->class instanceof Stmt\Class_) ? null : resolveSpecial(nameStr($node->class), $c);
|
|
252
|
+
addFact($c, ['t' => 'new', 'class' => $cls, 'args' => argsDesc($node->args, $c)], $node);
|
|
253
|
+
} elseif ($node instanceof Expr\PropertyFetch || $node instanceof Expr\NullsafePropertyFetch) {
|
|
254
|
+
addFact($c, ['t' => 'fetch', 'recv' => desc($node->var, $c), 'prop' => nameStr($node->name), 'write' => false], $node);
|
|
255
|
+
} elseif ($node instanceof Expr\StaticPropertyFetch) {
|
|
256
|
+
addFact($c, ['t' => 'sfetch', 'class' => resolveSpecial(nameStr($node->class), $c), 'prop' => nameStr($node->name)], $node);
|
|
257
|
+
} elseif ($node instanceof Expr\ClassConstFetch) {
|
|
258
|
+
$cls = resolveSpecial(nameStr($node->class), $c);
|
|
259
|
+
if ($cls) addFact($c, ['t' => 'classref', 'class' => $cls, 'const' => nameStr($node->name)], $node);
|
|
260
|
+
} elseif ($node instanceof Stmt\Return_ && $node->expr) {
|
|
261
|
+
addFact($c, ['t' => 'return', 'expr' => desc($node->expr, $c)], $node);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// closures: params typed become var types; closures get context of enclosing call
|
|
265
|
+
if ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) {
|
|
266
|
+
foreach ($node->params as $p) {
|
|
267
|
+
if ($p->var instanceof Expr\Variable && is_string($p->var->name)) {
|
|
268
|
+
$ts = typeList($p->type);
|
|
269
|
+
if ($ts) addFact($c, ['t' => 'vartype', 'var' => $p->var->name, 'types' => $ts, 'src' => 'closure_param'], $node);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// push call context for closures passed as arguments (used by migrations, Route groups, Schedule)
|
|
275
|
+
$pushed = false;
|
|
276
|
+
if (($node instanceof Expr\StaticCall || $node instanceof Expr\MethodCall || $node instanceof Expr\FuncCall)) {
|
|
277
|
+
$hasClosure = false; $clo = null;
|
|
278
|
+
foreach ($node->args as $a) { if ($a instanceof Node\Arg && ($a->value instanceof Expr\Closure || $a->value instanceof Expr\ArrowFunction)) { $hasClosure = true; $clo = $a->value; break; } }
|
|
279
|
+
if ($hasClosure) {
|
|
280
|
+
// end: last line of the call (closure bodies of Broadcast::channel / Pest test() / Route groups lie inside)
|
|
281
|
+
// cparams: the closure's parameter names (the first one of a channel callback is the authenticated user)
|
|
282
|
+
$cc = ['m' => nameStr($node->name), 'line' => $node->getStartLine(), 'end' => $node->getEndLine(),
|
|
283
|
+
'cparams' => array_values(array_map(fn($p) => ($p->var instanceof Expr\Variable && is_string($p->var->name)) ? $p->var->name : '?', $clo->params))];
|
|
284
|
+
if ($node instanceof Expr\StaticCall) $cc['class'] = resolveSpecial(nameStr($node->class), $c);
|
|
285
|
+
$first = $node->args[0] ?? null;
|
|
286
|
+
if ($first instanceof Node\Arg && $first->value instanceof Scalar\String_) $cc['arg0'] = $first->value->value;
|
|
287
|
+
$c->closureStack[] = $cc; $pushed = true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
foreach ($node->getSubNodeNames() as $sn) {
|
|
291
|
+
walk($node->$sn, $c, $nc);
|
|
292
|
+
}
|
|
293
|
+
if ($pushed) array_pop($c->closureStack);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function literal($e) {
|
|
297
|
+
if ($e instanceof Scalar\String_) return $e->value;
|
|
298
|
+
if ($e instanceof Scalar\Int_ || $e instanceof Scalar\Float_) return $e->value;
|
|
299
|
+
if ($e instanceof Expr\ConstFetch) { $n = strtolower($e->name->toString()); return $n === 'true' ? true : ($n === 'false' ? false : null); }
|
|
300
|
+
if ($e instanceof Expr\Array_) {
|
|
301
|
+
$o = [];
|
|
302
|
+
foreach ($e->items as $it) {
|
|
303
|
+
if ($it === null) continue;
|
|
304
|
+
$v = literal($it->value);
|
|
305
|
+
if ($it->key instanceof Scalar\String_) $o[$it->key->value] = $v; else $o[] = $v;
|
|
306
|
+
}
|
|
307
|
+
return $o;
|
|
308
|
+
}
|
|
309
|
+
if ($e instanceof Expr\ClassConstFetch && nameStr($e->name) === 'class') return ['__class__' => ltrim(nameStr($e->class), '\\')];
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function funcLike($fn, Ctx $c, \PhpParser\NameContext $nc, ?string $doc): array {
|
|
314
|
+
$c->facts = []; $c->closureStack = [];
|
|
315
|
+
$params = [];
|
|
316
|
+
$docParams = [];
|
|
317
|
+
if ($doc && preg_match_all('/@param\s+(\S+)\s+\$(\w+)/', $doc, $mm, PREG_SET_ORDER)) {
|
|
318
|
+
foreach ($mm as $m) $docParams[$m[2]] = docTypes($m[1], $nc);
|
|
319
|
+
}
|
|
320
|
+
foreach ($fn->params as $p) {
|
|
321
|
+
$pn = ($p->var instanceof Expr\Variable && is_string($p->var->name)) ? $p->var->name : '?';
|
|
322
|
+
$ts = typeList($p->type);
|
|
323
|
+
if (!$ts && isset($docParams[$pn])) $ts = $docParams[$pn];
|
|
324
|
+
$params[] = ['name' => $pn, 'types' => $ts, 'promoted' => $p->flags !== 0, 'line' => $p->getStartLine()];
|
|
325
|
+
}
|
|
326
|
+
$returns = typeList($fn->returnType ?? null);
|
|
327
|
+
$nullable = ($fn->returnType ?? null) instanceof Node\NullableType
|
|
328
|
+
|| (($fn->returnType ?? null) instanceof Node\UnionType && in_array('null', array_map(fn($t) => strtolower((string) $t), $fn->returnType->types), true));
|
|
329
|
+
if ($doc && preg_match('/@return\s+(\S+)/', $doc, $m)) {
|
|
330
|
+
$returns = array_values(array_unique(array_merge($returns, docTypes($m[1], $nc))));
|
|
331
|
+
}
|
|
332
|
+
if (isset($fn->stmts) && $fn->stmts !== null) walk($fn->stmts, $c, $nc);
|
|
333
|
+
if ($fn instanceof Expr\ArrowFunction) walk($fn->expr, $c, $nc);
|
|
334
|
+
$skel = (isset($fn->stmts) && $fn->stmts !== null) ? skelStmts($fn->stmts, $c) : [];
|
|
335
|
+
return ['params' => $params, 'returns' => $returns, 'returns_nullable' => $nullable, 'facts' => $c->facts, 'skel' => $skel];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function classLike(Stmt\ClassLike $cl, Ctx $c, \PhpParser\NameContext $nc, string $rel): array {
|
|
339
|
+
$kind = $cl instanceof Stmt\Interface_ ? 'interface' : ($cl instanceof Stmt\Trait_ ? 'trait' : ($cl instanceof Stmt\Enum_ ? 'enum' : 'class'));
|
|
340
|
+
$fq = isset($cl->namespacedName) && $cl->namespacedName ? $cl->namespacedName->toString() : ('class@anonymous:' . $rel . ':' . $cl->getStartLine());
|
|
341
|
+
$c->class = $fq;
|
|
342
|
+
$extends = [];
|
|
343
|
+
if ($cl instanceof Stmt\Class_ && $cl->extends) $extends = [ltrim($cl->extends->toString(), '\\')];
|
|
344
|
+
if ($cl instanceof Stmt\Interface_) $extends = array_map(fn($n) => ltrim($n->toString(), '\\'), $cl->extends);
|
|
345
|
+
$c->parent = $extends[0] ?? null;
|
|
346
|
+
$impl = ($cl instanceof Stmt\Class_ || $cl instanceof Stmt\Enum_) ? array_map(fn($n) => ltrim($n->toString(), '\\'), $cl->implements) : [];
|
|
347
|
+
$traits = []; $props = []; $methods = []; $consts = []; $cases = [];
|
|
348
|
+
$cdoc = docText($cl);
|
|
349
|
+
$docProps = [];
|
|
350
|
+
if ($cdoc && preg_match_all('/@property(?:-read|-write)?\s+(\S+)\s+\$(\w+)/', $cdoc, $mm, PREG_SET_ORDER)) {
|
|
351
|
+
foreach ($mm as $m) $docProps[] = ['name' => $m[2], 'types' => docTypes($m[1], $nc), 'src' => 'doc'];
|
|
352
|
+
}
|
|
353
|
+
foreach ($cl->stmts as $s) {
|
|
354
|
+
if ($s instanceof Stmt\TraitUse) {
|
|
355
|
+
foreach ($s->traits as $t) $traits[] = ltrim($t->toString(), '\\');
|
|
356
|
+
} elseif ($s instanceof Stmt\Property) {
|
|
357
|
+
$pd = docText($s);
|
|
358
|
+
$ts = typeList($s->type);
|
|
359
|
+
if (!$ts && $pd && preg_match('/@var\s+(\S+)/', $pd, $m)) $ts = docTypes($m[1], $nc);
|
|
360
|
+
foreach ($s->props as $p) {
|
|
361
|
+
$props[] = ['name' => $p->name->toString(), 'types' => $ts, 'static' => $s->isStatic(), 'default' => $p->default ? literal($p->default) : null,
|
|
362
|
+
'line' => $s->getStartLine(), 'doc' => $pd];
|
|
363
|
+
}
|
|
364
|
+
} elseif ($s instanceof Stmt\EnumCase) {
|
|
365
|
+
$cases[] = ['name' => $s->name->toString(), 'line' => $s->getStartLine()];
|
|
366
|
+
} elseif ($s instanceof Stmt\ClassConst) {
|
|
367
|
+
foreach ($s->consts as $k) $consts[] = ['name' => $k->name->toString(), 'value' => literal($k->value), 'line' => $s->getStartLine()];
|
|
368
|
+
} elseif ($s instanceof Stmt\ClassMethod) {
|
|
369
|
+
$md = docText($s);
|
|
370
|
+
$f = funcLike($s, $c, $nc, $md);
|
|
371
|
+
if ($s->name->toString() === '__construct') {
|
|
372
|
+
foreach ($f['params'] as $p) if ($p['promoted']) $props[] = ['name' => $p['name'], 'types' => $p['types'], 'static' => false, 'default' => null, 'line' => $p['line'], 'doc' => null, 'promoted' => true];
|
|
373
|
+
}
|
|
374
|
+
$attrNames = [];
|
|
375
|
+
foreach ($s->attrGroups as $ag) foreach ($ag->attrs as $at) $attrNames[] = ltrim($at->name->toString(), '\\');
|
|
376
|
+
$methods[] = ['name' => $s->name->toString(), 'static' => $s->isStatic(), 'abstract' => $s->isAbstract() || $cl instanceof Stmt\Interface_,
|
|
377
|
+
'attributes' => $attrNames,
|
|
378
|
+
'visibility' => $s->isPublic() ? 'public' : ($s->isProtected() ? 'protected' : 'private'),
|
|
379
|
+
'line' => $s->getStartLine(), 'end_line' => $s->getEndLine(), 'doc' => $md] + $f;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
foreach ($docProps as $dp) $props[] = $dp + ['static' => false, 'default' => null, 'line' => $cl->getStartLine(), 'doc' => null];
|
|
383
|
+
$abstract = $cl instanceof Stmt\Class_ ? $cl->isAbstract() : false;
|
|
384
|
+
return ['kind' => $kind, 'fqcn' => $fq, 'extends' => $extends, 'implements' => $impl, 'traits' => $traits, 'abstract' => $abstract,
|
|
385
|
+
'line' => $cl->getStartLine(), 'end_line' => $cl->getEndLine(), 'doc' => $cdoc, 'props' => $props, 'consts' => $consts, 'cases' => $cases, 'methods' => $methods];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Collect class-likes anywhere in the tree (incl. anonymous classes in `return new class ...`). */
|
|
389
|
+
class Collector extends \PhpParser\NodeVisitorAbstract {
|
|
390
|
+
public array $classes = []; public array $functions = [];
|
|
391
|
+
public function __construct(public string $rel, public \PhpParser\NameContext $nc) {}
|
|
392
|
+
public function enterNode(Node $n) {
|
|
393
|
+
if ($n instanceof Stmt\ClassLike) { $this->classes[] = $n; }
|
|
394
|
+
if ($n instanceof Stmt\Function_) { $this->functions[] = $n; }
|
|
395
|
+
if ($n instanceof Expr\New_ && $n->class instanceof Stmt\Class_) { $this->classes[] = $n->class; }
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
$out = fopen('php://stdout', 'w');
|
|
401
|
+
foreach ($files as $rel) {
|
|
402
|
+
$path = $root . '/' . $rel;
|
|
403
|
+
$code = @file_get_contents($path);
|
|
404
|
+
if ($code === false) continue;
|
|
405
|
+
$GLOBALS['branchyMemo'] = [];
|
|
406
|
+
$rec = ['file' => $rel, 'classes' => [], 'functions' => [], 'top' => null, 'error' => null, 'namespace' => null, 'uses' => []];
|
|
407
|
+
try {
|
|
408
|
+
$ast = $parser->parse($code);
|
|
409
|
+
$nr = new NameResolver(null, ['preserveOriginalNames' => true, 'replaceNodes' => true]);
|
|
410
|
+
$tr = new NodeTraverser(); $tr->addVisitor($nr);
|
|
411
|
+
$ast = $tr->traverse($ast);
|
|
412
|
+
$nc = $nr->getNameContext();
|
|
413
|
+
foreach ($ast as $st) {
|
|
414
|
+
if ($st instanceof Stmt\Namespace_) { $rec['namespace'] = $st->name ? $st->name->toString() : null; }
|
|
415
|
+
}
|
|
416
|
+
$col = new Collector($rel, $nc);
|
|
417
|
+
$t2 = new NodeTraverser(); $t2->addVisitor($col); $t2->traverse($ast);
|
|
418
|
+
foreach ($col->classes as $cl) {
|
|
419
|
+
$c = new Ctx();
|
|
420
|
+
$rec['classes'][] = classLike($cl, $c, $nc, $rel);
|
|
421
|
+
}
|
|
422
|
+
foreach ($col->functions as $fn) {
|
|
423
|
+
$c = new Ctx();
|
|
424
|
+
$f = funcLike($fn, $c, $nc, docText($fn));
|
|
425
|
+
$rec['functions'][] = ['name' => $fn->namespacedName ? $fn->namespacedName->toString() : $fn->name->toString(), 'line' => $fn->getStartLine(), 'end_line' => $fn->getEndLine(), 'doc' => docText($fn)] + $f;
|
|
426
|
+
}
|
|
427
|
+
// top-level (file scope) code: routes, config, console schedules, migrations' `return new class`
|
|
428
|
+
$c = new Ctx();
|
|
429
|
+
$top = [];
|
|
430
|
+
foreach ($ast as $st) {
|
|
431
|
+
$stmts = $st instanceof Stmt\Namespace_ ? $st->stmts : [$st];
|
|
432
|
+
foreach ($stmts as $s) {
|
|
433
|
+
if ($s instanceof Stmt\ClassLike || $s instanceof Stmt\Function_ || $s instanceof Stmt\Use_ || $s instanceof Stmt\GroupUse) continue;
|
|
434
|
+
$top[] = $s;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
$c->facts = [];
|
|
438
|
+
walk($top, $c, $nc);
|
|
439
|
+
$rec['top'] = $c->facts;
|
|
440
|
+
// config files: literal structure of `return [...]` with env() calls
|
|
441
|
+
if (str_starts_with($rel, 'config/')) {
|
|
442
|
+
foreach ($top as $s) if ($s instanceof Stmt\Return_ && $s->expr instanceof Expr\Array_) $rec['config'] = configTree($s->expr, $c);
|
|
443
|
+
}
|
|
444
|
+
if (preg_match('#^routes/#', $rel)) {
|
|
445
|
+
$rec['routes'] = routeWalk($top, ['prefix' => '', 'middleware' => [], 'name' => '', 'controller' => null], $c);
|
|
446
|
+
}
|
|
447
|
+
} catch (\Throwable $e) {
|
|
448
|
+
$rec['error'] = get_class($e) . ': ' . $e->getMessage();
|
|
449
|
+
}
|
|
450
|
+
fwrite($out, json_encode($rec, JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR) . "\n");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** config/*.php: walk returned array; leaves carry env() reads and literal defaults. */
|
|
454
|
+
function configTree(Expr\Array_ $a, Ctx $c, string $prefix = ''): array {
|
|
455
|
+
$out = [];
|
|
456
|
+
foreach ($a->items as $it) {
|
|
457
|
+
if ($it === null || !($it->key instanceof Scalar\String_)) continue;
|
|
458
|
+
$key = ($prefix === '' ? '' : $prefix . '.') . $it->key->value;
|
|
459
|
+
$node = ['key' => $key, 'line' => $it->getStartLine(), 'envs' => [], 'literal' => null, 'children' => []];
|
|
460
|
+
$v = $it->value;
|
|
461
|
+
if ($v instanceof Expr\Array_) {
|
|
462
|
+
$node['children'] = configTree($v, $c, $key);
|
|
463
|
+
}
|
|
464
|
+
// collect env() calls in the value (not in nested arrays' children which are handled recursively)
|
|
465
|
+
$finder = new \PhpParser\NodeFinder();
|
|
466
|
+
$calls = ($v instanceof Expr\Array_) ? [] : $finder->find($v, fn($n) => $n instanceof Expr\FuncCall && $n->name instanceof Node\Name && $n->name->toString() === 'env');
|
|
467
|
+
foreach ($calls as $call) {
|
|
468
|
+
$arg0 = $call->args[0] ?? null; $arg1 = $call->args[1] ?? null;
|
|
469
|
+
if ($arg0 instanceof Node\Arg && $arg0->value instanceof Scalar\String_) {
|
|
470
|
+
$node['envs'][] = ['env' => $arg0->value->value, 'default' => ($arg1 instanceof Node\Arg) ? literal($arg1->value) : null, 'line' => $call->getStartLine()];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (!($v instanceof Expr\Array_)) {
|
|
474
|
+
$node['literal'] = literal($v);
|
|
475
|
+
if ($node['literal'] === null && $calls && count($calls) === 1) {
|
|
476
|
+
$d = $node['envs'][0]['default'] ?? null; if (is_string($d)) $node['default_literal'] = $d;
|
|
477
|
+
}
|
|
478
|
+
$cfg = $finder->find($v, fn($n) => $n instanceof Expr\FuncCall && $n->name instanceof Node\Name && $n->name->toString() === 'config');
|
|
479
|
+
foreach ($cfg as $call) {
|
|
480
|
+
$a0 = $call->args[0] ?? null;
|
|
481
|
+
if ($a0 instanceof Node\Arg && $a0->value instanceof Scalar\String_) $node['config_refs'][] = $a0->value->value;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
$out[] = $node;
|
|
485
|
+
}
|
|
486
|
+
return $out;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** routes/*.php: Route facade chains with group context (prefix/middleware/name/controller). */
|
|
490
|
+
function routeChain(Expr $e): ?array {
|
|
491
|
+
// returns list of [name, args, line] from root Route:: static call outward
|
|
492
|
+
$chain = [];
|
|
493
|
+
while ($e instanceof Expr\MethodCall) { array_unshift($chain, [nameStr($e->name), $e->args, $e->getStartLine()]); $e = $e->var; }
|
|
494
|
+
if ($e instanceof Expr\StaticCall && nameStr($e->class) !== null && preg_match('/(^|\\\\)Route$/', nameStr($e->class))) {
|
|
495
|
+
array_unshift($chain, [nameStr($e->name), $e->args, $e->getStartLine()]);
|
|
496
|
+
return $chain;
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function strList($e): array {
|
|
501
|
+
if ($e instanceof Node\Arg) $e = $e->value;
|
|
502
|
+
$l = literal($e);
|
|
503
|
+
if (is_string($l)) return [$l];
|
|
504
|
+
if (is_array($l)) return array_values(array_filter(array_map(fn($x) => is_string($x) ? $x : (is_array($x) && isset($x['__class__']) ? $x['__class__'] : null), $l)));
|
|
505
|
+
if ($e instanceof Expr\ClassConstFetch) return [ltrim(nameStr($e->class), '\\')];
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
function routeWalk(array $stmts, array $ctx, Ctx $c): array {
|
|
509
|
+
$routes = [];
|
|
510
|
+
$verbs = ['get','post','put','patch','delete','options','any','match'];
|
|
511
|
+
foreach ($stmts as $s) {
|
|
512
|
+
if (!($s instanceof Stmt\Expression)) continue;
|
|
513
|
+
$chain = routeChain($s->expr);
|
|
514
|
+
if (!$chain) continue;
|
|
515
|
+
$local = $ctx; $route = null; $groupBody = null; $resource = null; $localName = ''; $localMw = []; $wheres = [];
|
|
516
|
+
foreach ($chain as [$m, $args, $line]) {
|
|
517
|
+
$lm = strtolower((string)$m);
|
|
518
|
+
if ($lm === 'prefix') { $p = strList($args[0] ?? null)[0] ?? ''; $local['prefix'] = trim($local['prefix'] . '/' . trim($p, '/'), '/'); }
|
|
519
|
+
elseif ($lm === 'middleware') { $mw = []; foreach ($args as $a) $mw = array_merge($mw, strList($a)); if ($route || $resource) $localMw = array_merge($localMw, $mw); else $local['middleware'] = array_merge($local['middleware'], $mw); }
|
|
520
|
+
elseif ($lm === 'withoutmiddleware') { /* recorded as-is */ $local['without_middleware'] = strList($args[0] ?? null); }
|
|
521
|
+
elseif ($lm === 'name' || $lm === 'as') { $n = strList($args[0] ?? null)[0] ?? ''; if ($route || $resource) $localName .= $n; else $local['name'] .= $n; }
|
|
522
|
+
elseif ($lm === 'controller') { $local['controller'] = strList($args[0] ?? null)[0] ?? null; }
|
|
523
|
+
elseif ($lm === 'group') {
|
|
524
|
+
$a0 = $args[0] ?? null; $a1 = $args[1] ?? null;
|
|
525
|
+
if ($a0 instanceof Node\Arg && $a0->value instanceof Expr\Array_ && $a1) {
|
|
526
|
+
$opts = literal($a0->value) ?: [];
|
|
527
|
+
if (isset($opts['prefix'])) $local['prefix'] = trim($local['prefix'] . '/' . trim((string)$opts['prefix'], '/'), '/');
|
|
528
|
+
if (isset($opts['middleware'])) $local['middleware'] = array_merge($local['middleware'], (array)$opts['middleware']);
|
|
529
|
+
if (isset($opts['as'])) $local['name'] .= $opts['as'];
|
|
530
|
+
$a0 = $a1;
|
|
531
|
+
}
|
|
532
|
+
if ($a0 instanceof Node\Arg && ($a0->value instanceof Expr\Closure)) $groupBody = $a0->value->stmts;
|
|
533
|
+
}
|
|
534
|
+
elseif (in_array($lm, $verbs, true)) {
|
|
535
|
+
$methods = $lm === 'match' ? strList($args[0] ?? null) : [strtoupper($lm)];
|
|
536
|
+
$uriArg = $lm === 'match' ? ($args[1] ?? null) : ($args[0] ?? null);
|
|
537
|
+
$actArg = $lm === 'match' ? ($args[2] ?? null) : ($args[1] ?? null);
|
|
538
|
+
$uri = strList($uriArg)[0] ?? '?';
|
|
539
|
+
$action = null;
|
|
540
|
+
if ($actArg instanceof Node\Arg) {
|
|
541
|
+
$v = $actArg->value;
|
|
542
|
+
if ($v instanceof Expr\Array_ && count($v->items) === 2) {
|
|
543
|
+
$cls = $v->items[0]->value; $mth = $v->items[1]->value;
|
|
544
|
+
$action = ['class' => ($cls instanceof Expr\ClassConstFetch) ? ltrim(nameStr($cls->class), '\\') : null, 'method' => literal($mth)];
|
|
545
|
+
} elseif ($v instanceof Scalar\String_) {
|
|
546
|
+
if (str_contains($v->value, '@')) { [$cl, $mt] = explode('@', $v->value, 2); $action = ['class' => $cl, 'method' => $mt]; }
|
|
547
|
+
elseif ($local['controller']) $action = ['class' => $local['controller'], 'method' => $v->value];
|
|
548
|
+
} elseif ($v instanceof Expr\ClassConstFetch) {
|
|
549
|
+
$action = ['class' => ltrim(nameStr($v->class), '\\'), 'method' => '__invoke'];
|
|
550
|
+
} elseif ($v instanceof Expr\Closure || $v instanceof Expr\ArrowFunction) {
|
|
551
|
+
$action = ['closure' => true];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
$route = ['methods' => $methods, 'uri' => $uri, 'action' => $action, 'line' => $line];
|
|
555
|
+
}
|
|
556
|
+
elseif ($lm === 'apiresource' || $lm === 'resource') {
|
|
557
|
+
$resource = ['name' => strList($args[0] ?? null)[0] ?? '?', 'class' => strList($args[1] ?? null)[0] ?? null, 'api' => $lm === 'apiresource', 'line' => $line, 'only' => null, 'except' => null];
|
|
558
|
+
}
|
|
559
|
+
elseif ($lm === 'only' && $resource) { $resource['only'] = strList($args[0] ?? null); }
|
|
560
|
+
elseif ($lm === 'except' && $resource) { $resource['except'] = strList($args[0] ?? null); }
|
|
561
|
+
elseif ($lm === 'where' || $lm === 'wherenumber' || $lm === 'whereuuid') { }
|
|
562
|
+
}
|
|
563
|
+
if ($route) {
|
|
564
|
+
$route['prefix'] = $local['prefix'];
|
|
565
|
+
$route['full_uri'] = '/' . trim($local['prefix'] . '/' . trim($route['uri'], '/'), '/');
|
|
566
|
+
$route['middleware'] = array_values(array_unique(array_merge($local['middleware'], $localMw)));
|
|
567
|
+
$route['name'] = $local['name'] . $localName;
|
|
568
|
+
$routes[] = $route;
|
|
569
|
+
} elseif ($resource) {
|
|
570
|
+
$acts = $resource['api'] ? ['index'=>['GET',''], 'store'=>['POST',''], 'show'=>['GET','/{id}'], 'update'=>['PUT','/{id}'], 'destroy'=>['DELETE','/{id}']]
|
|
571
|
+
: ['index'=>['GET',''], 'create'=>['GET','/create'], 'store'=>['POST',''], 'show'=>['GET','/{id}'], 'edit'=>['GET','/{id}/edit'], 'update'=>['PUT','/{id}'], 'destroy'=>['DELETE','/{id}']];
|
|
572
|
+
foreach ($acts as $a => [$verb, $suffix]) {
|
|
573
|
+
if ($resource['only'] && !in_array($a, $resource['only'], true)) continue;
|
|
574
|
+
if ($resource['except'] && in_array($a, $resource['except'], true)) continue;
|
|
575
|
+
$uri = trim($resource['name'], '/') . $suffix;
|
|
576
|
+
$routes[] = ['methods' => [$verb], 'uri' => $uri, 'action' => ['class' => $resource['class'], 'method' => $a], 'line' => $resource['line'],
|
|
577
|
+
'prefix' => $local['prefix'], 'full_uri' => '/' . trim($local['prefix'] . '/' . $uri, '/'),
|
|
578
|
+
'middleware' => array_values(array_unique(array_merge($local['middleware'], $localMw))), 'name' => $local['name'] . $resource['name'] . '.' . $a, 'resource' => true];
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if ($groupBody !== null) {
|
|
582
|
+
$routes = array_merge($routes, routeWalk($groupBody, $local, $c));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return $routes;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
/* ---------------- control skeleton (for deterministic guard/gating analysis) ----------------
|
|
590
|
+
* A compact tree of the statements/expressions that matter for branch evaluation. Every node
|
|
591
|
+
* carries r=[lo,hi): the range of fact indices emitted inside it, so the Python evaluator can
|
|
592
|
+
* mark whole regions dead under an assumption (e.g. "the new-inventory flag is on").
|
|
593
|
+
*/
|
|
594
|
+
function rng($n, Ctx $c): array { return $c->ranges[spl_object_id($n)] ?? [0, 0]; }
|
|
595
|
+
|
|
596
|
+
$GLOBALS['branchyMemo'] = [];
|
|
597
|
+
function branchy($e): bool {
|
|
598
|
+
$memo = &$GLOBALS['branchyMemo'];
|
|
599
|
+
if (!($e instanceof Node)) return false;
|
|
600
|
+
$id = spl_object_id($e);
|
|
601
|
+
if (isset($memo[$id])) return $memo[$id];
|
|
602
|
+
$r = false;
|
|
603
|
+
if ($e instanceof Expr\Ternary || $e instanceof Expr\BinaryOp\BooleanAnd || $e instanceof Expr\BinaryOp\BooleanOr
|
|
604
|
+
|| $e instanceof Expr\BinaryOp\LogicalAnd || $e instanceof Expr\BinaryOp\LogicalOr || $e instanceof Expr\BinaryOp\Coalesce
|
|
605
|
+
|| $e instanceof Expr\Match_ || ($e instanceof Expr\Assign && $e->var instanceof Expr\Variable)
|
|
606
|
+
|| $e instanceof Expr\Closure || $e instanceof Expr\ArrowFunction) {
|
|
607
|
+
$r = true;
|
|
608
|
+
} else {
|
|
609
|
+
foreach ($e->getSubNodeNames() as $sn) {
|
|
610
|
+
$v = $e->$sn;
|
|
611
|
+
foreach ((is_array($v) ? $v : [$v]) as $x) { if (branchy($x)) { $r = true; break 2; } }
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return $memo[$id] = $r;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function skelArgs(array $args, Ctx $c): array {
|
|
618
|
+
$o = [];
|
|
619
|
+
foreach ($args as $a) { $o[] = ($a instanceof Node\Arg) ? skelExpr($a->value, $c) : ['k' => '?']; }
|
|
620
|
+
return $o;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function skelExpr($e, Ctx $c): array {
|
|
624
|
+
if ($e === null) return ['k' => 'lit', 'v' => null];
|
|
625
|
+
$r = rng($e, $c);
|
|
626
|
+
if ($e instanceof Scalar\String_ || $e instanceof Scalar\Int_ || $e instanceof Scalar\Float_) return ['k' => 'lit', 'v' => $e->value, 'r' => $r];
|
|
627
|
+
if ($e instanceof Expr\ConstFetch) {
|
|
628
|
+
$n = strtolower($e->name->toString());
|
|
629
|
+
if ($n === 'true') return ['k' => 'lit', 'v' => true, 'r' => $r];
|
|
630
|
+
if ($n === 'false') return ['k' => 'lit', 'v' => false, 'r' => $r];
|
|
631
|
+
if ($n === 'null') return ['k' => 'lit', 'v' => null, 'r' => $r];
|
|
632
|
+
return ['k' => '?', 'r' => $r];
|
|
633
|
+
}
|
|
634
|
+
if ($e instanceof Expr\ClassConstFetch && nameStr($e->name) !== 'class') return ['k' => 'const', 'class' => resolveSpecial(nameStr($e->class), $c), 'n' => nameStr($e->name), 'r' => $r];
|
|
635
|
+
if ($e instanceof Expr\Variable) return is_string($e->name) ? ['k' => 'var', 'n' => $e->name, 'r' => $r] : ['k' => '?', 'r' => $r];
|
|
636
|
+
if ($e instanceof Expr\BooleanNot) return ['k' => 'not', 'x' => skelExpr($e->expr, $c), 'r' => $r];
|
|
637
|
+
if ($e instanceof Expr\BinaryOp\BooleanAnd || $e instanceof Expr\BinaryOp\LogicalAnd) return ['k' => 'and', 'l' => skelExpr($e->left, $c), 'rr' => skelExpr($e->right, $c), 'r' => $r];
|
|
638
|
+
if ($e instanceof Expr\BinaryOp\BooleanOr || $e instanceof Expr\BinaryOp\LogicalOr) return ['k' => 'or', 'l' => skelExpr($e->left, $c), 'rr' => skelExpr($e->right, $c), 'r' => $r];
|
|
639
|
+
if ($e instanceof Expr\BinaryOp\Identical || $e instanceof Expr\BinaryOp\NotIdentical || $e instanceof Expr\BinaryOp\Equal || $e instanceof Expr\BinaryOp\NotEqual) {
|
|
640
|
+
return ['k' => 'cmp', 'op' => $e->getOperatorSigil(), 'l' => skelExpr($e->left, $c), 'rr' => skelExpr($e->right, $c), 'r' => $r];
|
|
641
|
+
}
|
|
642
|
+
if ($e instanceof Expr\BinaryOp\Coalesce) return ['k' => 'coal', 'l' => skelExpr($e->left, $c), 'rr' => skelExpr($e->right, $c), 'r' => $r];
|
|
643
|
+
if ($e instanceof Expr\Ternary) return ['k' => 'tern', 'c' => skelExpr($e->cond, $c), 't' => $e->if ? skelExpr($e->if, $c) : null, 'e' => skelExpr($e->else, $c), 'r' => $r];
|
|
644
|
+
if ($e instanceof Expr\Assign && $e->var instanceof Expr\Variable && is_string($e->var->name)) return ['k' => 'asg', 'n' => $e->var->name, 'x' => skelExpr($e->expr, $c), 'r' => $r];
|
|
645
|
+
if ($e instanceof Expr\Cast\Bool_) return ['k' => 'cast', 'to' => 'bool', 'x' => skelExpr($e->expr, $c), 'r' => $r];
|
|
646
|
+
if ($e instanceof Expr\Cast) return ['k' => 'cast', 'to' => 'other', 'x' => skelExpr($e->expr, $c), 'r' => $r];
|
|
647
|
+
if ($e instanceof Expr\Isset_) return ['k' => '?', 'r' => $r];
|
|
648
|
+
if ($e instanceof Expr\Array_) {
|
|
649
|
+
$o = ['k' => 'arr', 'empty' => count($e->items) === 0, 'r' => $r];
|
|
650
|
+
if (branchy($e)) { $o['ch'] = []; foreach ($e->items as $it) if ($it) $o['ch'][] = skelExpr($it->value, $c); }
|
|
651
|
+
return $o;
|
|
652
|
+
}
|
|
653
|
+
if ($e instanceof Expr\New_) return ['k' => 'new', 'args' => skelArgs($e->args, $c), 'r' => $r];
|
|
654
|
+
if ($e instanceof Expr\Match_) {
|
|
655
|
+
$arms = [];
|
|
656
|
+
foreach ($e->arms as $a) {
|
|
657
|
+
$arms[] = ['conds' => $a->conds === null ? null : array_map(fn($x) => skelExpr($x, $c), $a->conds), 'body' => skelExpr($a->body, $c), 'r' => rng($a->body, $c)];
|
|
658
|
+
}
|
|
659
|
+
return ['k' => 'match', 'c' => skelExpr($e->cond, $c), 'arms' => $arms, 'r' => $r];
|
|
660
|
+
}
|
|
661
|
+
if ($e instanceof Expr\Throw_ || $e instanceof Expr\Exit_) return ['k' => 'term', 'r' => $r];
|
|
662
|
+
if ($e instanceof Expr\Closure) {
|
|
663
|
+
$ps = []; foreach ($e->params as $p) if ($p->var instanceof Expr\Variable && is_string($p->var->name)) $ps[] = $p->var->name;
|
|
664
|
+
return ['k' => 'clo', 'params' => $ps, 'b' => skelStmts($e->stmts, $c), 'r' => $r];
|
|
665
|
+
}
|
|
666
|
+
if ($e instanceof Expr\ArrowFunction) {
|
|
667
|
+
$ps = []; foreach ($e->params as $p) if ($p->var instanceof Expr\Variable && is_string($p->var->name)) $ps[] = $p->var->name;
|
|
668
|
+
return ['k' => 'clo', 'params' => $ps, 'x' => skelExpr($e->expr, $c), 'r' => $r];
|
|
669
|
+
}
|
|
670
|
+
if ($e instanceof Expr\MethodCall || $e instanceof Expr\NullsafeMethodCall || $e instanceof Expr\StaticCall || $e instanceof Expr\FuncCall) {
|
|
671
|
+
$o = ['k' => 'call', 'f' => $c->nodeFact[spl_object_id($e)] ?? null, 'args' => skelArgs($e->args, $c), 'r' => $r];
|
|
672
|
+
if ($e instanceof Expr\FuncCall && $e->name instanceof Node\Name) $o['fn'] = strtolower($e->name->toString());
|
|
673
|
+
if (($e instanceof Expr\MethodCall || $e instanceof Expr\NullsafeMethodCall)) {
|
|
674
|
+
$o['m'] = nameStr($e->name);
|
|
675
|
+
if (branchy($e->var) || $e->var instanceof Expr\MethodCall || $e->var instanceof Expr\FuncCall || $e->var instanceof Expr\StaticCall) $o['recv'] = skelExpr($e->var, $c);
|
|
676
|
+
}
|
|
677
|
+
return $o;
|
|
678
|
+
}
|
|
679
|
+
// anything else: keep children only if they contain branching
|
|
680
|
+
$o = ['k' => '?', 'r' => $r];
|
|
681
|
+
if (branchy($e)) {
|
|
682
|
+
$o['ch'] = [];
|
|
683
|
+
foreach ($e->getSubNodeNames() as $sn) {
|
|
684
|
+
$v = $e->$sn;
|
|
685
|
+
foreach ((is_array($v) ? $v : [$v]) as $x) {
|
|
686
|
+
if ($x instanceof Node\Arg) $x = $x->value;
|
|
687
|
+
if ($x instanceof Expr) $o['ch'][] = skelExpr($x, $c);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return $o;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Variables assigned anywhere in a subtree (for havoc at loop/branch joins). */
|
|
695
|
+
function assignedVars($n, array &$out): void {
|
|
696
|
+
if (is_array($n)) { foreach ($n as $x) assignedVars($x, $out); return; }
|
|
697
|
+
if (!($n instanceof Node) || $n instanceof Expr\Closure || $n instanceof Expr\ArrowFunction) return;
|
|
698
|
+
if (($n instanceof Expr\Assign || $n instanceof Expr\AssignOp || $n instanceof Expr\AssignRef) && $n->var instanceof Expr\Variable && is_string($n->var->name)) $out[$n->var->name] = true;
|
|
699
|
+
if ($n instanceof Stmt\Foreach_) {
|
|
700
|
+
foreach ([$n->keyVar, $n->valueVar] as $v) if ($v instanceof Expr\Variable && is_string($v->name)) $out[$v->name] = true;
|
|
701
|
+
}
|
|
702
|
+
foreach ($n->getSubNodeNames() as $sn) assignedVars($n->$sn, $out);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function skelStmts(array $stmts, Ctx $c): array {
|
|
706
|
+
$o = [];
|
|
707
|
+
foreach ($stmts as $s) $o[] = skelStmt($s, $c);
|
|
708
|
+
return $o;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function skelBlock(string $kind, Node $s, array $pre, array $blocks, Ctx $c): array {
|
|
712
|
+
$av = []; assignedVars($blocks, $av);
|
|
713
|
+
return ['k' => 'blk', 'kind' => $kind, 'pre' => $pre, 'b' => array_map(fn($b) => skelStmts($b, $c), $blocks), 'av' => array_keys($av), 'r' => rng($s, $c)];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function skelStmt($s, Ctx $c): array {
|
|
717
|
+
$r = rng($s, $c);
|
|
718
|
+
if ($s instanceof Stmt\If_) {
|
|
719
|
+
$ei = [];
|
|
720
|
+
foreach ($s->elseifs as $x) $ei[] = ['c' => skelExpr($x->cond, $c), 'b' => skelStmts($x->stmts, $c), 'r' => rng($x, $c), 'line' => $x->getStartLine()];
|
|
721
|
+
return ['k' => 'if', 'c' => skelExpr($s->cond, $c), 'line' => $s->getStartLine(), 't' => skelStmts($s->stmts, $c), 'tr' => $s->stmts ? [rng($s->stmts[0], $c)[0], rng(end($s->stmts), $c)[1]] : [0, 0],
|
|
722
|
+
'ei' => $ei, 'e' => $s->else ? skelStmts($s->else->stmts, $c) : null, 'er' => $s->else ? rng($s->else, $c) : null, 'r' => $r];
|
|
723
|
+
}
|
|
724
|
+
if ($s instanceof Stmt\Return_) return ['k' => 'ret', 'x' => $s->expr ? skelExpr($s->expr, $c) : null, 'line' => $s->getStartLine(), 'r' => $r];
|
|
725
|
+
if ($s instanceof Stmt\Continue_ || $s instanceof Stmt\Break_) return ['k' => 'brk', 'r' => $r];
|
|
726
|
+
if ($s instanceof Stmt\Expression) {
|
|
727
|
+
$e = $s->expr;
|
|
728
|
+
if ($e instanceof Expr\Throw_ || $e instanceof Expr\Exit_) return ['k' => 'term', 'r' => $r];
|
|
729
|
+
return ['k' => 'x', 'x' => skelExpr($e, $c), 'r' => $r];
|
|
730
|
+
}
|
|
731
|
+
if ($s instanceof Stmt\Foreach_) return skelBlock('foreach', $s, [skelExpr($s->expr, $c)], [$s->stmts], $c);
|
|
732
|
+
if ($s instanceof Stmt\While_) return skelBlock('while', $s, [skelExpr($s->cond, $c)], [$s->stmts], $c);
|
|
733
|
+
if ($s instanceof Stmt\Do_) return skelBlock('do', $s, [], [$s->stmts], $c);
|
|
734
|
+
if ($s instanceof Stmt\For_) return skelBlock('for', $s, [], [$s->stmts], $c);
|
|
735
|
+
if ($s instanceof Stmt\Switch_) return skelBlock('switch', $s, [skelExpr($s->cond, $c)], array_map(fn($k) => $k->stmts, $s->cases), $c);
|
|
736
|
+
if ($s instanceof Stmt\TryCatch) {
|
|
737
|
+
$av = []; assignedVars(array_map(fn($k) => $k->stmts, $s->catches), $av);
|
|
738
|
+
return ['k' => 'try', 'b' => skelStmts($s->stmts, $c), 'catches' => array_map(fn($k) => skelStmts($k->stmts, $c), $s->catches),
|
|
739
|
+
'fin' => $s->finally ? skelStmts($s->finally->stmts, $c) : [], 'av' => array_keys($av), 'r' => $r];
|
|
740
|
+
}
|
|
741
|
+
if ($s instanceof Stmt\Block) return ['k' => 'seq', 'b' => skelStmts($s->stmts, $c), 'r' => $r];
|
|
742
|
+
return ['k' => 'other', 'r' => $r];
|
|
743
|
+
}
|