PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.8.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.8.9
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
betterdocs / includes / Dependencies / SuperClosure / Analyzer / AstAnalyzer.php

AstAnalyzer.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 3.8.9, at includes/Dependencies/SuperClosure/Analyzer/AstAnalyzer.php

149 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace WPDeveloper\BetterDocs\Dependencies\SuperClosure\Analyzer;
2
3 use WPDeveloper\BetterDocs\Dependencies\SuperClosure\Analyzer\Visitor\ThisDetectorVisitor;
4 use WPDeveloper\BetterDocs\Dependencies\SuperClosure\Exception\ClosureAnalysisException;
5 use WPDeveloper\BetterDocs\Dependencies\SuperClosure\Analyzer\Visitor\ClosureLocatorVisitor;
6 use WPDeveloper\BetterDocs\Dependencies\SuperClosure\Analyzer\Visitor\MagicConstantVisitor;
7 use WPDeveloper\BetterDocs\Dependencies\PhpParser\NodeTraverser;
8 use WPDeveloper\BetterDocs\Dependencies\PhpParser\PrettyPrinter\Standard as NodePrinter;
9 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Error as ParserError;
10 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Node\Expr\Variable as VariableNode;
11 use WPDeveloper\BetterDocs\Dependencies\PhpParser\NodeVisitor\NameResolver;
12 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Parser as CodeParser;
13 use WPDeveloper\BetterDocs\Dependencies\PhpParser\ParserFactory;
14 use WPDeveloper\BetterDocs\Dependencies\PhpParser\Lexer\Emulative as EmulativeLexer;
15
16 /**
17 * This is the AST based analyzer.
18 *
19 * We're using reflection and AST-based code parser to analyze a closure and
20 * determine its code and context using the nikic/php-parser library. The AST
21 * based analyzer and has more capabilities than the token analyzer, but is,
22 * unfortunately, about 25 times slower.
23 */
24 class AstAnalyzer extends ClosureAnalyzer
25 {
26 protected function determineCode(array &$data)
27 {
28 // Find the closure by traversing through a AST of the code.
29 // Note: This also resolves class names to their FQCNs while traversing.
30 $this->locateClosure($data);
31
32 // Make a second pass through the AST, but only through the closure's
33 // nodes, to resolve any magic constants to literal values.
34 $traverser = new NodeTraverser;
35 $traverser->addVisitor(new MagicConstantVisitor($data['location']));
36 $traverser->addVisitor($thisDetector = new ThisDetectorVisitor);
37 $data['ast'] = $traverser->traverse([$data['ast']])[0];
38 $data['hasThis'] = $thisDetector->detected;
39
40 // Bounce the updated AST down to a string representation of the code.
41 $data['code'] = (new NodePrinter)->prettyPrint([$data['ast']]);
42 }
43
44 /**
45 * Parses the closure's code and produces an abstract syntax tree (AST).
46 *
47 * @param array $data
48 *
49 * @throws ClosureAnalysisException if there is an issue finding the closure
50 */
51 private function locateClosure(array &$data)
52 {
53 try {
54 $locator = new ClosureLocatorVisitor($data['reflection']);
55 $fileAst = $this->getFileAst($data['reflection']);
56
57 $fileTraverser = new NodeTraverser;
58 $fileTraverser->addVisitor(new NameResolver);
59 $fileTraverser->addVisitor($locator);
60 $fileTraverser->traverse($fileAst);
61 } catch (ParserError $e) {
62 // @codeCoverageIgnoreStart
63 throw new ClosureAnalysisException(
64 'There was an error analyzing the closure code.', 0, $e
65 );
66 // @codeCoverageIgnoreEnd
67 }
68
69 $data['ast'] = $locator->closureNode;
70 if (!$data['ast']) {
71 // @codeCoverageIgnoreStart
72 throw new ClosureAnalysisException(
73 'The closure was not found within the abstract syntax tree.'
74 );
75 // @codeCoverageIgnoreEnd
76 }
77
78 $data['location'] = $locator->location;
79 }
80
81 /**
82 * Returns the variables that in the "use" clause of the closure definition.
83 * These are referred to as the "used variables", "static variables", or
84 * "closed upon variables", "context" of the closure.
85 *
86 * @param array $data
87 */
88 protected function determineContext(array &$data)
89 {
90 // Get the variable names defined in the AST
91 $refs = 0;
92 $vars = array_map(function ($node) use (&$refs) {
93 if ($node->byRef) {
94 $refs++;
95 }
96 if ($node->var instanceof VariableNode) {
97 // For PHP-Parser >=4.0
98 return $node->var->name;
99 } else {
100 // For PHP-Parser <4.0
101 return $node->var;
102 }
103 }, $data['ast']->uses);
104 $data['hasRefs'] = ($refs > 0);
105
106 // Get the variable names and values using reflection
107 $values = $data['reflection']->getStaticVariables();
108
109 // Combine the names and values to create the canonical context.
110 foreach ($vars as $name) {
111 if (isset($values[$name])) {
112 $data['context'][$name] = $values[$name];
113 }
114 }
115 }
116
117 /**
118 * @param \ReflectionFunction $reflection
119 *
120 * @throws ClosureAnalysisException
121 *
122 * @return \WPDeveloper\BetterDocs\Dependencies\PhpParser\Node[]
123 */
124 private function getFileAst(\ReflectionFunction $reflection)
125 {
126 $fileName = $reflection->getFileName();
127 if (!file_exists($fileName)) {
128 throw new ClosureAnalysisException(
129 "The file containing the closure, \"{$fileName}\" did not exist."
130 );
131 }
132
133
134 return $this->getParser()->parse(file_get_contents($fileName));
135 }
136
137 /**
138 * @return CodeParser
139 */
140 private function getParser()
141 {
142 if (class_exists('WPDeveloper\BetterDocs\Dependencies\PhpParser\ParserFactory')) {
143 return (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
144 }
145
146 return new CodeParser(new EmulativeLexer);
147 }
148 }
149