final public function migrateFile()

in src/Migrations/FixmeCodeMigration.hack [83:208]


  final public function migrateFile(string $path, Script $root): Script {
    $errors_by_offset = $this->getTypecheckerErrorsForFile($path)
      |> Vec\map($$, $error ==> C\firstx($error['message']))
      |> Dict\group_by(
        $$,
        $error ==> offset_from_position($root, $error['line'], $error['start']),
      );

    // These need to be stored before we do any replacements, otherwise the
    // offsets are no longer valid.
    $token_at_offset = Dict\from_keys(
      Keyset\keys($errors_by_offset),
      $offset ==> {
        $node = find_node_at_offset($root, $offset);
        if ($node is Trivia) {
          return null;
        }
        return $node->getFirstTokenx();
      },
    ) |> Dict\filter_nulls($$);

    foreach ($errors_by_offset as $offset => $errors) {
      $token = $token_at_offset[$offset] ?? null;
      if ($token is null) {
        continue;
      }

      // Avoid inserting the same FIXME multiple times (it could happen if there
      // are multiple errors on the same line).
      $existing_fixme_codes = keyset[];

      // Although a FIXME may be on the previous line or even further away, to
      // be valid, it must be part of the trivia of a token that is on the same
      // line as the token with the error. [citation needed]
      $same_line = true;

      // Go backwards until we find the exact token that has the old FIXME in
      // its leading trivia.
      do {
        $old_trivia = $token->getLeading()->getChildren();

        // Find the index of the old HH_FIXME. If there are multiple such
        // HH_FIXMEs, we take the last one.
        for ($idx = C\count($old_trivia) - 1; $idx >= 0; --$idx) {
          $code = self::getFixMeCode($old_trivia[$idx]);
          if ($code is nonnull) {
            if (C\contains_key(static::OLD_CODES, $code)) {
              break;
            } else if (C\contains_key(static::NEW_CODES, $code)) {
              $existing_fixme_codes[] = $code;
            }
          }
          if ($old_trivia[$idx] is EndOfLine) {
            $same_line = false;
          }
        }

        if ($idx !== -1) {
          // We found the HH_FIXME to migrate.
          break;
        } else if ($same_line) {
          $token = $root->getPreviousToken($token);
          if ($token is nonnull) {
            foreach ($token->getTrailing()->getChildren() as $trivia) {
              if ($trivia is EndOfLine) {
                $token = null;
                break;
              }
            }
          }
        } else {
          $token = null;
        }
      } while ($token is nonnull);

      if ($token is null) {
        // We didn't find a FIXME to migrate, so proceed to the next set of
        // errors (leaving unFIXME'd errors behind).
        continue;
      }

      $new_fixmes = vec[];
      foreach ($errors as $error) {
        if (!C\contains_key($existing_fixme_codes, $error['code'])) {
          $new_fixmes[] = self::migrateFixMe($old_trivia[$idx], $error['code']);
        }
      }
      if (C\is_empty($new_fixmes)) {
        continue;
      }

      // We duplicate the old FIXME along with any whitespace following it, so
      // as to not mess up indentation.
      $whitespace = vec[];
      for (++$idx; $idx < C\count($old_trivia); ++$idx) {
        if ($old_trivia[$idx] is WhiteSpace || $old_trivia[$idx] is EndOfLine) {
          $whitespace[] = $old_trivia[$idx];
        } else {
          break;
        }
      }

      $new_trivia = vec[];
      foreach ($new_fixmes as $fixme) {
        $new_trivia[] = $fixme;
        foreach ($whitespace as $w) {
          $new_trivia[] = $w;
        }
      }

      $new_leading = NodeList::createMaybeEmptyList(
        Vec\concat(
          // take the old trivia up to, and including the old FIXME and its
          // following whitespace
          Vec\slice($old_trivia, 0, $idx),
          // insert the new FIXME(s) and whitespace after the old
          $new_trivia,
          Vec\slice($old_trivia, $idx),
        ),
      );

      $root = $root->replace($token, $token->withLeading($new_leading));
    }

    return $root;
  }