in src/Migrations/HSLMigration.hack [156:289]
public function migrateFile(string $path, Script $root): Script {
// find all the function calls
$nodes = $root->getDescendantsByType<FunctionCallExpression>();
// keep track of any HSL namespaces we may have to add to the top of the file
$found_namespaces = keyset[];
// check if any functions are in the replacement list
foreach ($nodes as $node) {
// bail if not in the config
$fn_name = $this->getFunctionName($node);
if (
$fn_name === null ||
!C\contains_key(self::PHP_HSL_REPLACEMENTS, $fn_name)
) {
continue;
}
// found a function to replace!
$replace_config = self::PHP_HSL_REPLACEMENTS[$fn_name];
$namespace = $replace_config['ns'];
$replacement = $replace_config['name'];
// build the replacement AST node
$new_node = $this->replaceFunctionName(
$node,
$namespace.'\\'.$replacement,
);
// possibly change argument order
$argument_order = $replace_config['argument_order'] ?? null;
if (
$argument_order !== null || ($replace_config['has_overrides'] ?? false)
) {
/*HHAST_FIXME[DontUseAsioJoin]*/
list($new_node, $found_namespaces) = \HH\Asio\join(
$this->maybeMutateArgumentsAsync(
$root,
$new_node,
$argument_order,
$path,
$found_namespaces,
),
);
}
// if we got null back here, it means the function call has unsupported arguments. forget it for now
if ($new_node === null) {
continue;
}
// we know we're rewriting the node, so now we know we need the namespace
$found_namespaces[] = $namespace;
// replace it in the ast
$root = $root->replace($node, $new_node);
// potentially change adjacent expressions to check for null instead of false
if ($replace_config['replace_false_with_null'] ?? false) {
$root = $this->maybeChangeFalseToNull($root, $new_node);
}
}
if (C\count($found_namespaces) === 0) {
return $root;
}
// add "use namespace" declarations at the top if they aren't already present
$declarations = $root->getDescendantsByType<INamespaceUseDeclaration>();
list($hsl_declarations, $suffixes) = $this->findUseDeclarations(
$declarations,
);
$count_before = C\count($suffixes);
// add new suffixes to the current list of suffixes
$suffixes = Keyset\union($suffixes, $found_namespaces);
// added any new suffixes?
if (C\count($suffixes) === $count_before) {
return $root;
}
// remove any current use statements for HH\Lib\* namespaces, we'll group them together
// Can't use ->getDescendantsByType<NodeList>() because NodeList is generic.
$lists = $root->getDescendantsOfType(NodeList::class);
foreach ($lists as $list) {
$children = $list->toVec();
$filtered = Vec\filter(
$children,
$c ==> !C\contains($hsl_declarations, $c),
);
if ($children !== $filtered) {
$root = $root->replace($list, new NodeList($filtered));
}
}
// build a possibly grouped namespace use declaration
$new_namespace_use_declaration = $this->buildUseDeclaration($suffixes);
// insert the new node: skip the <?hh sigil and namespace declaration if present,
// then insert before the first declaration that remains
foreach ($root->getChildren()['declarations']->getChildren() as $child) {
if ($child is MarkupSection) {
continue;
}
if ($child is NamespaceDeclaration) {
$body = $child->getBody();
// namespace Foo; style declaration, skip over it
if ($body is NamespaceEmptyBody) {
continue;
}
// namespace Foo { style declaration
// insert the use statement inside the braces, before the first child
invariant($body is NamespaceBody, 'expected NamespaceBody');
$child = $body->getDeclarationsx()->getChildren() |> C\firstx($$);
}
if ($child is INamespaceUseDeclaration) {
// next statement is another use declaration, remove the trailing newline
$last_token = $new_namespace_use_declaration->getLastTokenx();
$new_namespace_use_declaration = $new_namespace_use_declaration
->replace($last_token, $last_token->withTrailing(null));
}
$parent = $root->getParentOfDescendant($child) as NodeList<_>;
return $root->replace(
$parent,
$parent->insertBefore($child, $new_namespace_use_declaration),
);
}
invariant_violation('should not fail to insert new node');
}