in src/Linters/NamespacePrivateLinter.hack [18:130]
public function getLintErrorForNode(
this::TContext $context,
this::TNode $node,
): ?ASTLintError {
// Process all namespaces, use statements present in the file.
$namespaces_details = $node->getNamespaces();
// Go through namespace details and its uses to check for potential violations of private namespace boundaries.
foreach ($namespaces_details as $namespace_detail_shape) {
$current_namespace = $namespace_detail_shape['name'] ?? '';
$namespace_uses = Shapes::toArray($namespace_detail_shape['uses']);
foreach ($namespace_uses as $namespace_use_dict) {
foreach ($namespace_use_dict as $aliased_ns) {
if (
!$this->isPrivateNamespacePathAllowed(
$aliased_ns['name'],
$current_namespace,
)
) {
return new ASTLintError(
$this,
'Artifacts belonging to a namespace are private to the parent namespace',
$aliased_ns['use_clause'],
);
}
}
}
}
$traversed_qualified_names = varray[];
foreach (
$node->getDescendantsByType<QualifiedName>() as $qualified_name_token
) {
$name_token_key = '';
if ($qualified_name_token->hasParts()) {
$name_token_key = $qualified_name_token->getDescendantsByType<
NameToken,
>()
|> Vec\map(
$$,
$qualified_name_token ==> $qualified_name_token->getText(),
)
|> Str\join($$, '\\');
if (qualified_name_is_fully_qualified($qualified_name_token)) {
$name_token_key = '\\'.$name_token_key;
}
}
// If a qualified token has already been traversed or has no parts, then skip it.
if (
Str\is_empty($name_token_key) ||
C\contains($traversed_qualified_names, $name_token_key)
) {
continue;
}
// Add the current qualified name token to the traversed list.
$traversed_qualified_names[] = $name_token_key;
// get the namespace for each qualified name token that we encounter.
foreach ($namespaces_details as $ns) {
if ($ns['children']->isAncestorOf($qualified_name_token)) {
$current_namespace = $ns['name'] ?? '';
$fully_qualified_name_for_current_token = '';
$parent_node = $context->getParentOfDescendant($qualified_name_token);
if (
$parent_node is ScopeResolutionExpression ||
$parent_node is NameExpression
) {
$fully_qualified_name_for_current_token = $this->resolveScope(
$name_token_key,
$ns['uses']['namespaces'],
$current_namespace,
);
} else if ($parent_node is FunctionCallExpression) {
$fully_qualified_name_for_current_token = resolve_function(
$name_token_key,
$context,
$qualified_name_token,
)['name'];
} else if ($parent_node is SimpleTypeSpecifier) {
$fully_qualified_name_for_current_token = resolve_type(
$name_token_key,
$context,
$qualified_name_token,
)['name'];
}
/**
* In some cases, the current namespace is also a qualified name, so it gets filtered into a qualified name token.
* We do not want to check for private keyword in that case.
*/
if ($fully_qualified_name_for_current_token === $current_namespace) {
continue;
}
if (
!$this->isPrivateNamespacePathAllowed(
$fully_qualified_name_for_current_token,
$current_namespace,
)
) {
return new ASTLintError(
$this,
'Artifacts belonging to a namespace are private to the parent namespace',
$qualified_name_token,
);
}
}
}
}
return null;
}