in src/Node.php [486:579]
public function getImportTablesForCurrentScope() {
$namespaceDefinition = $this->getNamespaceDefinition();
// Use declarations can exist in either the global scope, or inside namespace declarations.
// http://php.net/manual/en/language.namespaces.importing.php#language.namespaces.importing.scope
//
// The only code allowed before a namespace declaration is a declare statement, and sub-namespaces are
// additionally unaffected by by import rules of higher-level namespaces. Therefore, we can make the assumption
// that we need not travel up the spine any further once we've found the current namespace.
// http://php.net/manual/en/language.namespaces.definition.php
if ($namespaceDefinition instanceof NamespaceDefinition) {
$topLevelNamespaceStatements = $namespaceDefinition->compoundStatementOrSemicolon instanceof Token
? $namespaceDefinition->parent->statementList // we need to start from the namespace definition.
: $namespaceDefinition->compoundStatementOrSemicolon->statements;
$namespaceFullStart = $namespaceDefinition->getFullStartPosition();
} else {
$topLevelNamespaceStatements = $this->getRoot()->statementList;
$namespaceFullStart = 0;
}
$nodeFullStart = $this->getFullStartPosition();
// TODO optimize performance
// Currently we rebuild the import tables on every call (and therefore every name resolution operation)
// It is likely that a consumer will attempt many consecutive name resolution requests within the same file.
// Therefore, we can consider optimizing on the basis of the "most recently used" import table set.
// The idea: Keep a single set of import tables cached based on a unique root node id, and invalidate
// cache whenever we attempt to resolve a qualified name with a different root node.
//
// In order to make this work, it will probably make sense to change the way we parse namespace definitions.
// https://github.com/Microsoft/tolerant-php-parser/issues/81
//
// Currently the namespace definition only includes a compound statement or semicolon token as one if it's children.
// Instead, we should move to a model where we parse future statements as a child rather than as a separate
// statement. This would enable us to retrieve all the information we would need to find the fully qualified
// name by simply traveling up the spine to find the first ancestor of type NamespaceDefinition.
$namespaceImportTable = $functionImportTable = $constImportTable = [];
$contents = $this->getFileContents();
foreach ($topLevelNamespaceStatements as $useDeclaration) {
if ($useDeclaration->getFullStartPosition() <= $namespaceFullStart) {
continue;
}
if ($useDeclaration->getFullStartPosition() > $nodeFullStart) {
break;
} elseif (!($useDeclaration instanceof NamespaceUseDeclaration)) {
continue;
}
// TODO fix getValues
foreach ((isset($useDeclaration->useClauses) ? $useDeclaration->useClauses->getValues() : []) as $useClause) {
$namespaceNamePartsPrefix = $useClause->namespaceName !== null ? $useClause->namespaceName->nameParts : [];
if ($useClause->groupClauses !== null && $useClause instanceof NamespaceUseClause) {
// use A\B\C\{D\E}; namespace import: ["E" => [A,B,C,D,E]]
// use A\B\C\{D\E as F}; namespace import: ["F" => [A,B,C,D,E]]
// use function A\B\C\{A, B} function import: ["A" => [A,B,C,A], "B" => [A,B,C]]
// use function A\B\C\{const A} const import: ["A" => [A,B,C,A]]
foreach ($useClause->groupClauses->children as $groupClause) {
if (!($groupClause instanceof NamespaceUseGroupClause)) {
continue;
}
$namespaceNameParts = \array_merge($namespaceNamePartsPrefix, $groupClause->namespaceName->nameParts);
$functionOrConst = $groupClause->functionOrConst ?? $useDeclaration->functionOrConst;
$alias = $groupClause->namespaceAliasingClause === null
? $groupClause->namespaceName->getLastNamePart()->getText($contents)
: $groupClause->namespaceAliasingClause->name->getText($contents);
$this->addToImportTable(
$alias, $functionOrConst, $namespaceNameParts, $contents,
$namespaceImportTable, $functionImportTable, $constImportTable
);
}
} else {
// use A\B\C; namespace import: ["C" => [A,B,C]]
// use A\B\C as D; namespace import: ["D" => [A,B,C]]
// use function A\B\C as D function import: ["D" => [A,B,C]]
// use A\B, C\D; namespace import: ["B" => [A,B], "D" => [C,D]]
$alias = $useClause->namespaceAliasingClause === null
? $useClause->namespaceName->getLastNamePart()->getText($contents)
: $useClause->namespaceAliasingClause->name->getText($contents);
$functionOrConst = $useDeclaration->functionOrConst;
$namespaceNameParts = $namespaceNamePartsPrefix;
$this->addToImportTable(
$alias, $functionOrConst, $namespaceNameParts, $contents,
$namespaceImportTable, $functionImportTable, $constImportTable
);
}
}
}
return [$namespaceImportTable, $functionImportTable, $constImportTable];
}