Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php
13473 views
1
<?php
2
3
final class PhutilXHPASTSyntaxHighlighterFuture extends FutureProxy {
4
5
private $source;
6
private $scrub;
7
8
public function __construct(Future $proxied, $source, $scrub = false) {
9
parent::__construct($proxied);
10
$this->source = $source;
11
$this->scrub = $scrub;
12
}
13
14
protected function didReceiveResult($result) {
15
try {
16
return $this->applyXHPHighlight($result);
17
} catch (Exception $ex) {
18
// XHP can't highlight source that isn't syntactically valid. Fall back
19
// to the fragment lexer.
20
$source = ($this->scrub
21
? preg_replace('/^.*\n/', '', $this->source)
22
: $this->source);
23
return id(new PhutilLexerSyntaxHighlighter())
24
->setConfig('lexer', new PhutilPHPFragmentLexer())
25
->setConfig('language', 'php')
26
->getHighlightFuture($source)
27
->resolve();
28
}
29
}
30
31
private function applyXHPHighlight($result) {
32
33
// We perform two passes here: one using the AST to find symbols we care
34
// about -- particularly, class names and function names. These are used
35
// in the crossreference stuff to link into Diffusion. After we've done our
36
// AST pass, we do a followup pass on the token stream to catch all the
37
// simple stuff like strings and comments.
38
39
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
40
$this->source,
41
$result);
42
43
$root = $tree->getRootNode();
44
45
$tokens = $root->getTokens();
46
$interesting_symbols = $this->findInterestingSymbols($root);
47
48
49
if ($this->scrub) {
50
// If we're scrubbing, we prepended "<?php\n" to the text to force the
51
// highlighter to treat it as PHP source. Now, we need to remove that.
52
53
$ok = false;
54
if (count($tokens) >= 2) {
55
if ($tokens[0]->getTypeName() === 'T_OPEN_TAG') {
56
if ($tokens[1]->getTypeName() === 'T_WHITESPACE') {
57
$ok = true;
58
}
59
}
60
}
61
62
if (!$ok) {
63
throw new Exception(
64
pht(
65
'Expected T_OPEN_TAG, T_WHITESPACE tokens at head of results '.
66
'for highlighting parse of PHP snippet.'));
67
}
68
69
// Remove the "<?php".
70
unset($tokens[0]);
71
72
$value = $tokens[1]->getValue();
73
if ((strlen($value) < 1) || ($value[0] != "\n")) {
74
throw new Exception(
75
pht(
76
'Expected "\\n" at beginning of T_WHITESPACE token at head of '.
77
'tokens for highlighting parse of PHP snippet.'));
78
}
79
80
$value = substr($value, 1);
81
$tokens[1]->overwriteValue($value);
82
}
83
84
$out = array();
85
foreach ($tokens as $key => $token) {
86
$value = $token->getValue();
87
$class = null;
88
$multi = false;
89
$attrs = array();
90
if (isset($interesting_symbols[$key])) {
91
$sym = $interesting_symbols[$key];
92
$class = $sym[0];
93
$attrs['data-symbol-context'] = idx($sym, 'context');
94
$attrs['data-symbol-name'] = idx($sym, 'symbol');
95
} else {
96
switch ($token->getTypeName()) {
97
case 'T_WHITESPACE':
98
break;
99
case 'T_DOC_COMMENT':
100
$class = 'dc';
101
$multi = true;
102
break;
103
case 'T_COMMENT':
104
$class = 'c';
105
$multi = true;
106
break;
107
case 'T_CONSTANT_ENCAPSED_STRING':
108
case 'T_ENCAPSED_AND_WHITESPACE':
109
case 'T_INLINE_HTML':
110
$class = 's';
111
$multi = true;
112
break;
113
case 'T_VARIABLE':
114
$class = 'nv';
115
break;
116
case 'T_OPEN_TAG':
117
case 'T_OPEN_TAG_WITH_ECHO':
118
case 'T_CLOSE_TAG':
119
$class = 'o';
120
break;
121
case 'T_LNUMBER':
122
case 'T_DNUMBER':
123
$class = 'm';
124
break;
125
case 'T_STRING':
126
static $magic = array(
127
'true' => true,
128
'false' => true,
129
'null' => true,
130
);
131
if (isset($magic[strtolower($value)])) {
132
$class = 'k';
133
break;
134
}
135
$class = 'nx';
136
break;
137
default:
138
$class = 'k';
139
break;
140
}
141
}
142
143
if ($class) {
144
$attrs['class'] = $class;
145
if ($multi) {
146
// If the token may have multiple lines in it, make sure each
147
// <span> crosses no more than one line so the lines can be put
148
// in a table, etc., later.
149
$value = phutil_split_lines($value, $retain_endings = true);
150
} else {
151
$value = array($value);
152
}
153
foreach ($value as $val) {
154
$out[] = phutil_tag('span', $attrs, $val);
155
}
156
} else {
157
$out[] = $value;
158
}
159
}
160
161
return phutil_implode_html('', $out);
162
}
163
164
private function findInterestingSymbols(XHPASTNode $root) {
165
// Class name symbols appear in:
166
// class X extends X implements X, X { ... }
167
// new X();
168
// $x instanceof X
169
// catch (X $x)
170
// function f(X $x)
171
// X::f();
172
// X::$m;
173
// X::CONST;
174
175
// These are PHP builtin tokens which can appear in a classname context.
176
// Don't link them since they don't go anywhere useful.
177
static $builtin_class_tokens = array(
178
'self' => true,
179
'parent' => true,
180
'static' => true,
181
);
182
183
// Fortunately XHPAST puts all of these in a special node type so it's
184
// easy to find them.
185
$result_map = array();
186
$class_names = $root->selectDescendantsOfType('n_CLASS_NAME');
187
foreach ($class_names as $class_name) {
188
foreach ($class_name->getTokens() as $key => $token) {
189
if (isset($builtin_class_tokens[$token->getValue()])) {
190
// This is something like "self::method()".
191
continue;
192
}
193
$result_map[$key] = array(
194
'nc', // "Name, Class"
195
'symbol' => $class_name->getConcreteString(),
196
);
197
}
198
}
199
200
// Function name symbols appear in:
201
// f()
202
203
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
204
foreach ($function_calls as $call) {
205
$call = $call->getChildByIndex(0);
206
if ($call->getTypeName() == 'n_SYMBOL_NAME') {
207
// This is a normal function call, not some $f() shenanigans.
208
foreach ($call->getTokens() as $key => $token) {
209
$result_map[$key] = array(
210
'nf', // "Name, Function"
211
'symbol' => $call->getConcreteString(),
212
);
213
}
214
}
215
}
216
217
// Upon encountering $x->y, link y without context, since $x is unknown.
218
219
$prop_access = $root->selectDescendantsOfType('n_OBJECT_PROPERTY_ACCESS');
220
foreach ($prop_access as $access) {
221
$right = $access->getChildByIndex(1);
222
if ($right->getTypeName() == 'n_INDEX_ACCESS') {
223
// otherwise $x->y[0] doesn't get highlighted
224
$right = $right->getChildByIndex(0);
225
}
226
if ($right->getTypeName() == 'n_STRING') {
227
foreach ($right->getTokens() as $key => $token) {
228
$result_map[$key] = array(
229
'na', // "Name, Attribute"
230
'symbol' => $right->getConcreteString(),
231
);
232
}
233
}
234
}
235
236
// Upon encountering x::y, try to link y with context x.
237
238
$static_access = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
239
foreach ($static_access as $access) {
240
$class = $access->getChildByIndex(0);
241
$right = $access->getChildByIndex(1);
242
if ($class->getTypeName() == 'n_CLASS_NAME' &&
243
($right->getTypeName() == 'n_STRING' ||
244
$right->getTypeName() == 'n_VARIABLE')) {
245
$classname = head($class->getTokens())->getValue();
246
$result = array(
247
'na',
248
'symbol' => ltrim($right->getConcreteString(), '$'),
249
);
250
if (!isset($builtin_class_tokens[$classname])) {
251
$result['context'] = $classname;
252
}
253
foreach ($right->getTokens() as $key => $token) {
254
$result_map[$key] = $result;
255
}
256
}
257
}
258
259
return $result_map;
260
}
261
262
}
263
264