PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.3 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 All 201 releases
betterdocs / includes / Dependencies / SuperClosure / Analyzer / AstAnalyzer.php

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

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