public function getLintErrorForNode()

in src/Linters/GroupUseStatementsLinter.hack [20:333]


  public function getLintErrorForNode(
    Script $_context,
    Script $script,
  ): ?ASTLintError {
    $error = false;

    $uses = dict[
      'function' => vec[],
      'namespace' => vec[],
      'type' => vec[],
    ];

    $result = dict[
      'function' => dict[],
      'namespace' => dict[],
      'type' => dict[],
    ];

    $use_kind = (INamespaceUseDeclaration $node): ?string ==> {
      foreach ($node->getChildren() as $child) {
        if ($child is FunctionToken) {
          return 'function';
        }

        if ($child is NamespaceToken) {
          return 'namespace';
        }

        if ($child is TypeToken) {
          return 'type';
        }
      }

      return null;
    };

    foreach (
      $script->getDescendantsByType<INamespaceUseDeclaration>() as $use_decl
    ) {
      if ($use_decl is NamespaceUseDeclaration) {
        foreach (
          $use_decl->getDescendantsByType<NamespaceUseClause>() as $use_clause
        ) {
          $parts = vec[];

          $name_leading = vec[];
          $name_trailing = vec[];

          $name = $use_clause->getName();
          if ($name is NameToken) {
            if ($name->getLeading()->isList()) {
              foreach ($name->getLeading()->getChildren() as $leading) {
                $name_leading[] = $leading;
              }
            }

            if ($name->getTrailing()->isList()) {
              foreach ($name->getTrailing()->getChildren() as $trailing) {
                $name_trailing[] = $trailing;
              }
            }

            $parts[] = $name->getText();
          } else if ($name is QualifiedName) {
            foreach ($name->getDescendantsByType<NameToken>() as $name_token) {
              if ($name_token->getLeading()->isList()) {
                foreach ($name_token->getLeading()->getChildren() as $leading) {
                  $name_leading[] = $leading;
                }
              }

              if ($name_token->getTrailing()->isList()) {
                foreach (
                  $name_token->getTrailing()->getChildren() as $trailing
                ) {
                  $name_trailing[] = $trailing;
                }
              }

              $parts[] = $name_token->getText();
            }
          }

          if (C\count($parts) > 1) {
            $alias = $use_clause->getAlias();

            $alias_trailing = vec[];
            if ($alias is nonnull) {
              if ($alias->getTrailing()->isList()) {
                foreach ($alias->getTrailing()->getChildren() as $trailing) {
                  $alias_trailing[] = $trailing;
                }
              }
            }

            $kind = $use_kind($use_decl);
            if ($kind is nonnull) {
              $uses[$kind][] = tuple(
                $use_decl,
                $parts,
                $alias?->getText(),
                $name_leading,
                $name_trailing,
                $alias_trailing,
                vec[],
              );
            }
          }
        }
      }

      if ($use_decl is NamespaceGroupUseDeclaration) {
        $parts = vec[];

        foreach (
          $use_decl->getChildrenByType<QualifiedName>() as $qualified_name
        ) {
          foreach (
            $qualified_name->getDescendantsByType<NameToken>() as $name_token
          ) {
            $parts[] = $name_token->getText();
          }
        }

        foreach (
          $use_decl->getDescendantsByType<NamespaceUseClause>() as $use_clause
        ) {
          $parts_item = vec[];

          $name_leading = vec[];
          $name_trailing = vec[];
          $comma_trailing = vec[];
          // ->getDescendantsByType<ListItem>() can not be used, because ListItem is generic
          foreach ($use_decl->getDescendantsOfType(ListItem::class) as $item) {
            if ($item->isAncestorOf($use_clause)) {
              $comma_tokens = $item->getChildrenByType<CommaToken>();
              if (!C\is_empty($comma_tokens)) {
                $first_comma_token = C\firstx($comma_tokens);
                if ($first_comma_token->getTrailing()->isList()) {
                  foreach (
                    $first_comma_token->getTrailing()->getChildren() as
                      $trailing
                  ) {
                    $comma_trailing[] = $trailing;
                  }
                }
              }
              break;
            }
          }

          $name = $use_clause->getName();
          if ($name is NameToken) {
            if ($name->getLeading()->isList()) {
              foreach ($name->getLeading()->getChildren() as $leading) {
                $name_leading[] = $leading;
              }
            }

            if ($name->getTrailing()->isList()) {
              foreach ($name->getTrailing()->getChildren() as $trailing) {
                $name_trailing[] = $trailing;
              }
            }

            $parts_item[] = $name->getText();
          } else if ($name is QualifiedName) {
            foreach ($name->getDescendantsByType<NameToken>() as $name_token) {
              if ($name_token->getLeading()->isList()) {
                foreach ($name_token->getLeading()->getChildren() as $leading) {
                  $name_leading[] = $leading;
                }
              }

              if ($name_token->getTrailing()->isList()) {
                foreach (
                  $name_token->getTrailing()->getChildren() as $trailing
                ) {
                  $name_trailing[] = $trailing;
                }
              }

              $parts_item[] = $name_token->getText();
            }
          }

          $parts_item = Vec\concat($parts, $parts_item);
          if (C\count($parts_item) > 1) {
            $alias = $use_clause->getAlias();

            $alias_trailing = vec[];
            if ($alias is nonnull) {
              if ($alias->getTrailing()->isList()) {
                foreach ($alias->getTrailing()->getChildren() as $trailing) {
                  $alias_trailing[] = $trailing;
                }
              }
            }

            $kind = $use_kind($use_decl);
            if ($kind is nonnull) {
              $uses[$kind][] = tuple(
                $use_decl,
                $parts_item,
                $alias?->getText(),
                $name_leading,
                $name_trailing,
                $alias_trailing,
                $comma_trailing,
              );
            }
          }
        }
      }
    }

    foreach ($uses as $kind => $use_kind_loop_var) {
      foreach ($use_kind_loop_var as $use) {
        list(
          $node,
          $parts,
          $alias,
          $name_leading,
          $name_trailing,
          $alias_trailing,
          $comma_trailing,
        ) = $use;

        $namespace = Str\join(Vec\take($parts, C\count($parts) - 1), '\\');
        $name = Str\join(Vec\drop($parts, C\count($parts) - 1), '\\');

        if (!C\contains_key($result[$kind], $namespace)) {
          $result[$kind][$namespace] = tuple(vec[], vec[]);
        } else {
          if (
            C\count($result[$kind][$namespace][0]) > 1 ||
            $result[$kind][$namespace][0][0] !== $node
          ) {
            $error = true;
          }
        }

        if (!C\contains($result[$kind][$namespace][0], $node)) {
          $result[$kind][$namespace][0][] = $node;
        }

        if (
          Dict\filter($result[$kind][$namespace][1], (
            (
              string,
              ?string,
              vec<Trivia>,
              vec<Trivia>,
              vec<Trivia>,
              vec<Trivia>,
            ) $n,
          ) ==> {
            return $n[0] === $name && $n[1] === $alias;
          })
          |> C\count($$) === 0
        ) {
          $drop = 0;
          foreach ($name_leading as $leading) {
            if (!$leading is WhiteSpace && !$leading is EndOfLine) {
              break;
            }

            $drop++;
          }
          $name_leading = Vec\drop($name_leading, $drop);

          $take_trailing = (vec<Trivia> $trailings) ==> {
            $take = C\count($trailings);
            foreach (Vec\reverse($trailings) as $trailing) {
              if (!$trailing is WhiteSpace && !$trailing is EndOfLine) {
                break;
              }

              $take--;
            }
            return Vec\take($trailings, $take);
          };

          $name_trailing = $take_trailing($name_trailing);
          $alias_trailing = $take_trailing($alias_trailing);
          $comma_trailing = $take_trailing($comma_trailing);

          $result[$kind][$namespace][1][] = tuple(
            $name,
            $alias,
            $name_leading,
            $name_trailing,
            $alias_trailing,
            $comma_trailing,
          );
        }
      }
    }

    if ($error) {
      return new ASTLintError(
        $this,
        'Use statements should be grouped',
        $script,
        () ==> $this->getFixedNode($script, Dict\map($result, (
          $result_kind,
        ) ==> {
          return Dict\filter($result_kind, ($value) ==> C\count($value[0]) > 1);
        })),
      );
    }

    return null;
  }