-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathQualifiedName.php
187 lines (166 loc) · 7.9 KB
/
QualifiedName.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft\PhpParser\Node;
use Microsoft\PhpParser\NamespacedNameInterface;
use Microsoft\PhpParser\NamespacedNameTrait;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\Expression\AnonymousFunctionCreationExpression;
use Microsoft\PhpParser\Node\Expression\ArrowFunctionCreationExpression;
use Microsoft\PhpParser\Node\Expression\CallExpression;
use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression;
use Microsoft\PhpParser\ResolvedName;
use Microsoft\PhpParser\Token;
use Microsoft\PhpParser\TokenKind;
class QualifiedName extends Node implements NamespacedNameInterface {
use NamespacedNameTrait;
/** @var Token|null */
public $globalSpecifier; // \_opt
/** @var RelativeSpecifier|null */
public $relativeSpecifier; // namespace\
/** @var array */
public $nameParts;
const CHILD_NAMES = [
'globalSpecifier',
'relativeSpecifier',
'nameParts'
];
/**
* Checks whether a QualifiedName is prefixed with a backslash global specifier.
* @return bool
*/
public function isFullyQualifiedName() : bool {
return isset($this->globalSpecifier);
}
/**
* Checks whether a QualifiedName begins with a "namespace" keyword
* @return bool
*/
public function isRelativeName() : bool {
return isset($this->relativeSpecifier);
}
/**
* Checks whether a Name includes at least one namespace separator (and is neither fully-qualified nor relative)
* @return bool
*/
public function isQualifiedName() : bool {
return
!$this->isFullyQualifiedName() &&
!$this->isRelativeName() &&
\count($this->nameParts) > 1; // at least one namespace separator
}
/**
* Checks whether a name is does not include a namespace separator.
* @return bool
*/
public function isUnqualifiedName() : bool {
return !($this->isFullyQualifiedName() || $this->isRelativeName() || $this->isQualifiedName());
}
/**
* Gets resolved name based on name resolution rules defined in:
* http://php.net/manual/en/language.namespaces.rules.php
*
* Returns null if one of the following conditions is met:
* - name resolution is not valid for this element (e.g. part of the name in a namespace definition)
* - name cannot be resolved (unqualified namespaced function/constant names that are not explicitly imported.)
*
* @return null|string|ResolvedName
* @throws \Exception
*/
public function getResolvedName($namespaceDefinition = null) {
// Name resolution not applicable to constructs that define symbol names or aliases.
if (($this->parent instanceof Node\Statement\NamespaceDefinition && $this->parent->name->getStartPosition() === $this->getStartPosition()) ||
$this->parent instanceof Node\Statement\NamespaceUseDeclaration ||
$this->parent instanceof Node\NamespaceUseClause ||
$this->parent instanceof Node\NamespaceUseGroupClause ||
$this->parent instanceof Node\TraitSelectOrAliasClause
) {
return null;
}
if (in_array($lowerText = strtolower($this->getText()), ["self", "static", "parent"])) {
return $lowerText;
}
// FULLY QUALIFIED NAMES
// - resolve to the name without leading namespace separator.
if ($this->isFullyQualifiedName()) {
return ResolvedName::buildName($this->nameParts, $this->getFileContents());
}
// RELATIVE NAMES
// - resolve to the name with namespace replaced by the current namespace.
// - if current namespace is global, strip leading namespace\ prefix.
if ($this->isRelativeName()) {
return $this->getNamespacedName();
}
[$namespaceImportTable, $functionImportTable, $constImportTable] = $this->getImportTablesForCurrentScope();
// QUALIFIED NAMES
// - first segment of the name is translated according to the current class/namespace import table.
// - If no import rule applies, the current namespace is prepended to the name.
if ($this->isQualifiedName()) {
return $this->tryResolveFromImportTable($namespaceImportTable) ?? $this->getNamespacedName();
}
// UNQUALIFIED NAMES
// - translated according to the current import table for the respective symbol type.
// (class-like => namespace import table, constant => const import table, function => function import table)
// - if no import rule applies:
// - all symbol types: if current namespace is global, resolve to global namespace.
// - class-like symbols: resolve from current namespace.
// - function or const: resolved at runtime (from current namespace, with fallback to global namespace).
if ($this->isConstantName()) {
$resolvedName = $this->tryResolveFromImportTable($constImportTable, /* case-sensitive */ true);
$namespaceDefinition = $this->getNamespaceDefinition();
if ($namespaceDefinition !== null && $namespaceDefinition->name === null) {
$resolvedName = $resolvedName ?? ResolvedName::buildName($this->nameParts, $this->getFileContents());
}
return $resolvedName;
} elseif ($this->parent instanceof CallExpression) {
$resolvedName = $this->tryResolveFromImportTable($functionImportTable);
if (($namespaceDefinition = $this->getNamespaceDefinition()) === null || $namespaceDefinition->name === null) {
$resolvedName = $resolvedName ?? ResolvedName::buildName($this->nameParts, $this->getFileContents());
}
return $resolvedName;
}
return $this->tryResolveFromImportTable($namespaceImportTable) ?? $this->getNamespacedName();
}
public function getLastNamePart() {
$parts = $this->nameParts;
for ($i = \count($parts) - 1; $i >= 0; $i--) {
// TODO - also handle reserved word tokens
if ($parts[$i]->kind === TokenKind::Name) {
return $parts[$i];
}
}
return null;
}
/**
* @param ResolvedName[] $importTable
*/
private function tryResolveFromImportTable($importTable, bool $isCaseSensitive = false): ?ResolvedName {
$content = $this->getFileContents();
$index = $this->nameParts[0]->getText($content);
// if (!$isCaseSensitive) {
// $index = strtolower($index);
// }
if(isset($importTable[$index])) {
$resolvedName = $importTable[$index];
$resolvedName->addNameParts(\array_slice($this->nameParts, 1), $content);
return $resolvedName;
}
return null;
}
private function isConstantName() : bool {
return
($this->parent instanceof Node\Statement\ExpressionStatement || $this->parent instanceof Expression) &&
!(
$this->parent instanceof Node\Expression\MemberAccessExpression || $this->parent instanceof CallExpression ||
$this->parent instanceof ObjectCreationExpression ||
$this->parent instanceof Node\Expression\ScopedPropertyAccessExpression || $this->parent instanceof AnonymousFunctionCreationExpression ||
$this->parent instanceof ArrowFunctionCreationExpression ||
($this->parent instanceof Node\Expression\BinaryExpression && $this->parent->operator->kind === TokenKind::InstanceOfKeyword)
);
}
public function getNameParts() : array {
return $this->nameParts;
}
}