diff --git a/bot/reviewbot/processing/review.py b/bot/reviewbot/processing/review.py
index fff2509882ce11c27d724cddc2eafabc1f580551..28f1d812669da06d51b0b09a47f33fea6072b18c 100644
--- a/bot/reviewbot/processing/review.py
+++ b/bot/reviewbot/processing/review.py
@@ -1,9 +1,13 @@
+"""Utilities for processing files and creating reviews."""
+
 from __future__ import annotations
 
 import json
 import os
 from enum import Enum
 from itertools import islice
+from typing import (Any, Final, Literal, Optional, TypedDict, TYPE_CHECKING,
+                    cast)
 
 from rbtools.api.errors import APIError
 
@@ -13,6 +17,15 @@
                                         normalize_platform_path)
 from reviewbot.utils.log import get_logger
 
+if TYPE_CHECKING:
+    from collections.abc import Iterator
+    from rbtools.api.resource import (
+        FileDiffItemResource,
+        ItemResource,
+        ReviewItemResource,
+        RootResource,
+    )
+
 
 #: The logger for the module.
 #:
@@ -21,7 +34,78 @@
 logger = get_logger(__name__, is_task_logger=False)
 
 
-class ReviewFileStatus(Enum):
+class BaseCommentData(TypedDict):
+    """Base class for comment data.
+
+    Version Added:
+        5.0
+    """
+
+    #: Whether an issue should be opened.
+    issue_opened: bool
+
+    #: Whether the comment text should be formatted using Markdown.
+    rich_text: bool
+
+    #: The text of the comment.
+    text: str
+
+
+class GeneralCommentData(BaseCommentData):
+    """Data for a general comment.
+
+    Version Added:
+        5.0
+    """
+
+
+class DiffCommentData(BaseCommentData):
+    """Data for a diff comment.
+
+    Version Added:
+        5.0
+    """
+
+    #: The ID of the FileDiff that the comment is on.
+    filediff_id: int
+
+    #: The row number that the comment starts on.
+    first_line: int
+
+    #: The number of lines that the comment spans.
+    num_lines: int
+
+
+class DiffChunk(TypedDict):
+    """Data for a diff chunk.
+
+    This corresponds with the data created in
+    :py:mod:`reviewboard.diffviewer.chunk_generator`. This definition includes
+    types for the keys we use, but omits anything that Review Bot doesn't need,
+    so it is not a complete representation of what comes back through the API.
+
+    Version Added:
+        5.0
+    """
+
+    #: The type of change for the chunk.
+    change: Literal['delete', 'equal', 'insert', 'replace']
+
+    #: The 0-based index of the chunk.
+    index: int
+
+    #: The rendered list of lines.
+    lines: list[list[Any]]
+
+    #: Metadata for the chunk.
+    meta: dict[str, Any]
+
+    #: The number of lines in the chunk.
+    numlines: int
+
+
+# TODO: When we're Python 3.11+, switch to StrEnum
+class ReviewFileStatus(str, Enum):
     """The change status of a file.
 
     Version Added:
@@ -35,11 +119,14 @@
     COPIED = 'copied'
 
     @classmethod
-    def for_filediff(cls, filediff):
+    def for_filediff(
+        cls,
+        filediff: FileDiffItemResource,
+    ) -> ReviewFileStatus:
         """Return a status for a FileDiff.
 
         Args:
-            filediff (rbtools.api.resource.Resource):
+            filediff (rbtools.api.resource.FileDiffItemResource):
                 The filediff resource.
 
         Returns:
@@ -56,10 +143,10 @@
             return cls(filediff.status)
 
 
-class File(object):
+class File:
     """Represents a file in the review.
 
-    Information about the file can be retreived through this class,
+    Information about the file can be retrieved through this class,
     including retrieving the actual body of the original or patched
     file.
 
@@ -70,16 +157,54 @@
     #:
     #: If a comment exceeds this count, it will be capped and a line range
     #: will be provided in the comment.
-    COMMENT_MAX_LINES = 10
-
-    def __init__(self, review, api_filediff):
+    COMMENT_MAX_LINES: Final[int] = 10
+
+    ######################
+    # Instance variables #
+    ######################
+
+    #: The name of the patched version of the file.
+    dest_file: str
+
+    #: The diff data from the server.
+    diff_data: ItemResource
+
+    #: The file extension.
+    file_extension: str
+
+    #: The name of the file (without the extension).
+    filename: str
+
+    #: Tde ID of the FileDiff.
+    id: int
+
+    #: The path to the patched file.
+    patched_file_path: Optional[str]
+
+    #: The review that owns this file.
+    review: Review
+
+    #: The name of the original version of the file.
+    source_file: str
+
+    #: The status of the file in the diff.
+    status: ReviewFileStatus
+
+    #: The resource for the FileDiff.
+    _api_filediff: FileDiffItemResource
+
+    def __init__(
+        self,
+        review: Review,
+        api_filediff: FileDiffItemResource,
+    ) -> None:
         """Initialize the File.
 
         Args:
             review (Review):
                 The review object.
 
-            api_filediff (rbtools.api.resource.Resource):
+            api_filediff (rbtools.api.resource.FileDiffItemResource):
                 The filediff resource.
         """
         self.review = review
@@ -94,14 +219,15 @@
             # We don't need to normalize again. Just copy.
             self.dest_file = self.source_file
         else:
-            self.dest_file = normalize_platform_path(api_filediff.dest_file)
+            self.dest_file = normalize_platform_path(
+                cast(str, api_filediff.dest_file))
 
         self.filename, self.file_extension = os.path.splitext(self.dest_file)
 
         self._api_filediff = api_filediff
 
     @property
-    def patched_file_contents(self):
+    def patched_file_contents(self) -> Optional[bytes]:
         """The patched contents of the file.
 
         Returns:
@@ -139,7 +265,7 @@
             raise
 
     @property
-    def original_file_contents(self):
+    def original_file_contents(self) -> Optional[bytes]:
         """The original contents of the file.
 
         Returns:
@@ -175,7 +301,7 @@
 
             raise
 
-    def get_patched_file_path(self):
+    def get_patched_file_path(self) -> Optional[str]:
         """Fetch the patched file and return the filename of it.
 
         Version Changed:
@@ -207,7 +333,7 @@
 
         return filename
 
-    def get_original_file_path(self):
+    def get_original_file_path(self) -> Optional[str]:
         """Fetch the original file and return the filename of it.
 
         Version Changed:
@@ -235,7 +361,12 @@
 
         return filename
 
-    def get_lines(self, first_line, num_lines=1, original=False):
+    def get_lines(
+        self,
+        first_line: int,
+        num_lines: int = 1,
+        original: bool = False,
+    ) -> list[str]:
         """Return the lines from the file in the given range.
 
         This can be used to extract lines from the original or modified file,
@@ -277,7 +408,10 @@
 
         return result
 
-    def apply_patch(self, root_target_dir):
+    def apply_patch(
+        self,
+        root_target_dir: str,
+    ) -> None:
         """Apply the patch for this file to the filesystem.
 
         The file will be written relative to the current directory.
@@ -303,8 +437,8 @@
 
         assert os.path.commonprefix((source_file, dest_file,
                                      root_target_dir)) == root_target_dir, (
-            '%r and %r must be located within %r'
-            % (source_file, dest_file, root_target_dir))
+            f'{source_file} and {dest_file} must be located within '
+            f'{root_target_dir}')
 
         if self.status == ReviewFileStatus.DELETED:
             try:
@@ -320,7 +454,7 @@
             if self.status == ReviewFileStatus.MOVED:
                 try:
                     os.rename(source_file, dest_file)
-                except Exception as e:
+                except Exception:
                     # We'll log and then continue, just creating the new file.
                     logger.warning('Unable to move source file "%s" to '
                                    'to "%s" for FileDiff ID=%s',
@@ -331,9 +465,19 @@
 
         self.patched_file_path = self.dest_file
 
-    def comment(self, text, first_line, num_lines=1, start_column=None,
-                error_code=None, issue=None, rich_text=False, original=False,
-                text_extra=None, severity=None):
+    def comment(
+        self,
+        text: str,
+        first_line: Optional[int],
+        num_lines: int = 1,
+        start_column: Optional[int] = None,
+        error_code: Optional[str] = None,
+        issue: Optional[bool] = None,
+        rich_text: Optional[bool] = False,
+        original: Optional[bool] = False,
+        text_extra: Optional[list[tuple[str, str]]] = None,
+        severity: Optional[str] = None,
+    ) -> None:
         """Make a comment on the file.
 
         Version Changed:
@@ -371,14 +515,14 @@
                 If True, the ``first_line`` argument corresponds to the line
                 number in the original file, instead of the patched file.
 
-            severity (str, optional):
-                A tool-specific, human-readable indication of the severity of
-                this comment.
-
             text_extra (list of tuple, optional):
                 Additional data to append to the text in ``Key: Value`` form.
                 Each item is an ordered tuple of ``(Key, Value``). These will
                 be placed after the default items ("Column" and "Error code").
+
+            severity (str, optional):
+                A tool-specific, human-readable indication of the severity of
+                this comment.
         """
         # Some tools report a first_line of 0 to mean a 'global comment' on a
         # particular file. For now, we handle this as a special case as
@@ -391,24 +535,29 @@
                         self._is_modified(first_line, num_lines))
 
         if modified:
-            extra = []
+            extra: list[tuple[str, Any]] = []
             real_line = self._translate_line_num(first_line)
+            assert real_line is not None
 
             if num_lines != 1:
                 if num_lines > self.COMMENT_MAX_LINES:
+                    last_line = first_line + num_lines - 1
                     extra.append((
                         'Lines',
-                        '%s-%s' % (first_line, first_line + num_lines - 1),
+                        f'{first_line}-{last_line}',
                     ))
                     num_lines = self.COMMENT_MAX_LINES
 
                 last_line = first_line + num_lines - 1
                 real_last_line = self._translate_line_num(last_line)
+                assert real_last_line is not None
                 num_lines = real_last_line - real_line + 1
 
             if issue is None:
                 issue = self.review.settings['open_issues']
 
+            assert issue is not None
+
             if start_column:
                 extra.append(('Column', start_column))
 
@@ -422,22 +571,26 @@
                 extra += text_extra
 
             if extra:
-                text = '%s\n\n%s' % (text, '\n'.join(
-                    '%s: %s' % (key, value)
+                text += '\n\n' + '\n'.join(
+                    f'{key}: {value}'
                     for key, value in extra
-                ))
+                )
 
-            data = {
+            data: DiffCommentData = {
                 'filediff_id': self.id,
                 'first_line': real_line,
                 'num_lines': num_lines,
                 'text': text,
                 'issue_opened': issue,
-                'rich_text': rich_text,
+                'rich_text': bool(rich_text),
             }
             self.review.comments.append(data)
 
-    def _translate_line_num(self, line_num, original=False):
+    def _translate_line_num(
+        self,
+        line_num: int,
+        original: bool = False,
+    ) -> Optional[int]:
         """Convert a file line number to a filediff line number.
 
         Args:
@@ -462,7 +615,12 @@
         except StopIteration:
             return None
 
-    def _is_modified(self, line_num, num_lines, original=False):
+    def _is_modified(
+        self,
+        line_num: int,
+        num_lines: int,
+        original: bool = False,
+    ) -> bool:
         """Return whether the given region is modified in the diff.
 
         A region is considered modified if any of the lines within are
@@ -497,7 +655,11 @@
 
         return False
 
-    def _iter_lines(self, first_line=None, original=False):
+    def _iter_lines(
+        self,
+        first_line: Optional[int] = None,
+        original: bool = False,
+    ) -> Iterator[tuple[DiffChunk, list[Any], int]]:
         """Iterate through lines in the diff data.
 
         This is a convenience function for iterating through chunks in the
@@ -514,9 +676,15 @@
             tuple:
             A 3-tuple containing:
 
-            1. The chunk dictionary.
-            2. The list of information for the current row.
-            3. The line number of the row.
+            Tuple:
+                0 (dict):
+                    The chunk dictionary.
+
+                1 (list):
+                    The current row.
+
+                2 (int):
+                    The line number of the current row.
         """
         # The index in a diff line of a chunk that the relevant (original vs.
         # patched) line number is stored at.
@@ -525,7 +693,7 @@
         else:
             line_num_index = 4
 
-        chunks = self.diff_data.chunks
+        chunks: list[DiffChunk] = self.diff_data.chunks
 
         if first_line is not None:
             # First, we need to find the chunk with the line number. For
@@ -539,13 +707,16 @@
                 # We didn't find the line, so bail.
                 return
 
+            assert first_chunk is not None
+            assert first_chunk_i is not None
+
             # We found a result. Sanity-check it and then start
             # iterating.
             assert first_row[line_num_index] == first_line
 
             # First, iterate through the remainder of the chunk where
             # the row was found, starting at that row.
-            for row in first_chunk.lines[first_row_i:]:
+            for row in first_chunk['lines'][first_row_i:]:
                 yield first_chunk, row, row[line_num_index]
 
             # Now we'll prepare the remainder of the chunks for iteration.
@@ -558,7 +729,15 @@
                 if row_line_num:
                     yield chunk, row, row_line_num
 
-    def _find_line_num_info(self, chunks, expected_line_num, line_num_index):
+    def _find_line_num_info(
+        self,
+        chunks: list[DiffChunk],
+        expected_line_num: int,
+        line_num_index: int,
+    ) -> tuple[Optional[DiffChunk],
+               Optional[int],
+               Optional[list[Any]],
+               Optional[int]]:
         """Find the chunk and row for an expected line number.
 
         This will perform a binary search through the provided chunks, looking
@@ -580,10 +759,18 @@
             tuple:
             A 4-tuple containing:
 
-            1. The chunk containing the line number.
-            2. The index of the chunk within the list of chunks.
-            3. The row containing the line number.
-            4. The index of the row within the chunk.
+            Tuple:
+                0 (dict):
+                    The chunk containing the line number.
+
+                1 (int):
+                    The index of the chunk within the list of chunks.
+
+                2 (str):
+                    The row containing the line number.
+
+                3 (int):
+                    The index of the row within the chunk.
 
             If the line number could not be found, these will all be ``None``.
         """
@@ -592,21 +779,21 @@
         found_row = None
         found_row_i = None
 
-        chunks = [
+        chunks_i = [
             (chunk_i, chunk)
             for chunk_i, chunk in enumerate(chunks)
-            if chunk.lines[0][line_num_index] != ''
+            if chunk['lines'][0][line_num_index] != ''
         ]
 
         low = 0
-        high = len(chunks) - 1
+        high = len(chunks_i) - 1
 
         while low <= high:
             mid = (low + high) // 2
-            chunk_i, chunk = chunks[mid]
-            chunk_lines = chunk.lines
-            chunk_linenum1 = chunk_lines[0][line_num_index]
-            chunk_linenum2 = chunk_lines[-1][line_num_index]
+            chunk_i, chunk = chunks_i[mid]
+            chunk_lines = chunk['lines']
+            chunk_linenum1 = cast(int, chunk_lines[0][line_num_index])
+            chunk_linenum2 = cast(int, chunk_lines[-1][line_num_index])
 
             if chunk_linenum1 <= expected_line_num <= chunk_linenum2:
                 # We found the chunk containing the line number. Now
@@ -629,27 +816,61 @@
         return found_chunk, found_chunk_i, found_row, found_row_i
 
 
-class Review(object):
+class Review:
     """An object which orchestrates the creation of a review."""
 
-    #: Additional text to show above the comments in the review.
-    body_top = ""
-
-    #: Additional text to show below the comments in the review.
-    body_bottom = ""
-
-    _VALID_FILEDIFF_STATUS_TYPES = {
+    _VALID_FILEDIFF_STATUS_TYPES: Final[set[str]] = {
         'copied',
         'deleted',
         'modified',
         'moved',
     }
 
-    def __init__(self, api_root, review_request_id, diff_revision, settings):
+    ######################
+    # Instance variables #
+    ######################
+
+    #: The API root for the Review Board server.
+    api_root: RootResource
+
+    #: Additional text to show above the comments in the review.
+    body_top: str
+
+    #: Additional text to show below the comments in the review.
+    body_bottom: str
+
+    #: The diff comments in the review.
+    comments: list[DiffCommentData]
+
+    #: The diff revision being reviewed.
+    diff_revision: int
+
+    #: The files to be reviewed.
+    files: list[File]
+
+    #: The general comments in the review.
+    general_comments: list[GeneralCommentData]
+
+    #: The patch contents.
+    patch: Optional[str]
+
+    #: The ID of the review request being reviewed.
+    review_request_id: int
+
+    #: The settings provided by the extension.
+    settings: dict[str, Any]
+
+    def __init__(
+        self,
+        api_root: RootResource,
+        review_request_id: int,
+        diff_revision: int,
+        settings: dict[str, Any],
+    ) -> None:
         """Initialize the review.
 
         Args:
-            api_root (rbtools.api.resource.Resource):
+            api_root (rbtools.api.resource.RootResource):
                 The API root.
 
             review_request_id (int):
@@ -663,6 +884,8 @@
                 The settings provided by the extension when triggering the
                 task.
         """
+        self.body_top = ''
+        self.body_bottom = ''
         self.api_root = api_root
         self.settings = settings
         self.review_request_id = review_request_id
@@ -671,7 +894,7 @@
         self.general_comments = []
 
         # Get the list of files.
-        files = []
+        files: list[File] = []
 
         if self.diff_revision:
             filediffs = api_root.get_files(
@@ -682,8 +905,7 @@
                 # Filter out binary files and symlinks.
                 if (getattr(filediff, 'binary', False) or
                     filediff.status not in self._VALID_FILEDIFF_STATUS_TYPES or
-                    ('is_symlink' in filediff.extra_data and
-                     filediff.extra_data['is_symlink'])):
+                    filediff.extra_data.get('is_symlink', False)):
                     continue
 
                 files.append(File(review=self,
@@ -691,7 +913,12 @@
 
         self.files = files
 
-    def general_comment(self, text, issue=None, rich_text=False):
+    def general_comment(
+        self,
+        text: str,
+        issue: Optional[bool] = None,
+        rich_text: bool = False,
+    ) -> None:
         """Make a general comment.
 
         Args:
@@ -710,7 +937,7 @@
             'rich_text': rich_text,
         })
 
-    def publish(self):
+    def publish(self) -> ReviewItemResource:
         """Upload the review to Review Board."""
         # Truncate comments to the maximum permitted amount to avoid
         # overloading the review and freezing the browser.
@@ -718,11 +945,13 @@
         num_comments = len(self.comments) + len(self.general_comments)
 
         if num_comments > max_comments:
-            warning = ('**Warning:** Showing %d of %d failures.'
-                       % (max_comments, num_comments))
+            warning = (
+                f'**Warning:** Showing {max_comments} of {num_comments} '
+                f'failures.'
+            )
 
             if self.body_top:
-                self.body_top = '%s\n%s' % (self.body_top, warning)
+                self.body_top = f'{self.body_top}\n{warning}'
             else:
                 self.body_top = warning
 
@@ -733,7 +962,7 @@
                 del self.comments[max_comments - len(self.general_comments):]
 
         bot_reviews = self.api_root.get_extension(
-            extension_name='reviewbotext.extension.ReviewBotExtension'
+            extension_name='reviewbotext.extension.ReviewBotExtension',
         ).get_review_bot_reviews()
 
         return bot_reviews.create(
@@ -745,30 +974,32 @@
             general_comments=json.dumps(self.general_comments))
 
     @property
-    def has_comments(self):
-        """Whether the review has comments."""
+    def has_comments(self) -> bool:
+        """Whether the review has comments.
+
+        Type:
+            bool
+        """
         return len(self.comments) + len(self.general_comments) != 0
 
     @property
-    def patch_contents(self):
+    def patch_contents(self) -> Optional[str]:
         """The contents of the patch.
 
-        Returns:
+        Type:
             str:
-            The contents of the patch associated with the review request and
-            diff revision.
         """
         if not hasattr(self, 'patch'):
             if not hasattr(self.api_root, 'get_diff'):
                 return None
 
-            self.patch = self.api_root.get_diff(
+            self.patch = cast(str, self.api_root.get_diff(
                 review_request_id=self.review_request_id,
-                diff_revision=self.diff_revision).get_patch().data
+                diff_revision=self.diff_revision).get_patch().data)
 
         return self.patch
 
-    def get_patch_file_path(self):
+    def get_patch_file_path(self) -> Optional[str]:
         """Fetch the patch and return the filename of it.
 
         Returns:
diff --git a/bot/reviewbot/processing/tests/test_file.py b/bot/reviewbot/processing/tests/test_file.py
index 2b9529a690e242f44fbd079f6215355d98e8a5b1..8533c935e2d574e6fc48f2917c3e3f9f9ffe8124 100644
--- a/bot/reviewbot/processing/tests/test_file.py
+++ b/bot/reviewbot/processing/tests/test_file.py
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import os
+from typing import TYPE_CHECKING
 
 import kgb
 from rbtools.api.errors import APIError
@@ -12,12 +13,26 @@
 from reviewbot.testing import TestCase
 from reviewbot.utils.filesystem import make_tempdir, tmpdirs
 
+if TYPE_CHECKING:
+    from reviewbot.processing.review import File, Review
+
 
 class FileTests(kgb.SpyAgency, TestCase):
     """Unit tests for reviewbot.processing.review.File."""
 
-    def setUp(self):
-        super(FileTests, self).setUp()
+    ######################
+    # Instance variables #
+    ######################
+
+    #: The file object to test.
+    review_file: File
+
+    #: The review object.
+    review: Review
+
+    def setUp(self) -> None:
+        """Set up the test case."""
+        super().setUp()
 
         self.review = self.create_review()
         self.review_file = self.create_review_file(
@@ -55,14 +70,14 @@
                     'change': 'insert',
                     'lines': [
                         'if foo():',
-                        '    sys.exit(1)'
+                        '    sys.exit(1)',
                     ],
                     'new_linenum': 69,
                     'old_linenum': 66,
                 },
             ]))
 
-    def test_apply_patch_with_add(self):
+    def test_apply_patch_with_add(self) -> None:
         """Testing File.apply_patch with added file"""
         tempdir = make_tempdir()
         filename = os.path.join(tempdir, 'docs', 'test.txt')
@@ -77,10 +92,10 @@
 
         self.assertTrue(os.path.exists(filename))
 
-        with open(filename, 'r') as fp:
+        with open(filename) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_add_and_abs_path(self):
+    def test_apply_patch_with_add_and_abs_path(self) -> None:
         """Testing File.apply_patch with added file and absolute path"""
         tempdir = make_tempdir()
         filename = os.path.join(tempdir, 'docs', 'test.txt')
@@ -95,10 +110,10 @@
 
         self.assertTrue(os.path.exists(filename))
 
-        with open(filename, 'r') as fp:
+        with open(filename) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_copy(self):
+    def test_apply_patch_with_copy(self) -> None:
         """Testing File.apply_patch with copied file"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -118,13 +133,13 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename1, 'r') as fp:
+        with open(filename1) as fp:
             self.assertEqual(fp.read(), 'This is a test.\n')
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_copy_and_missing(self):
+    def test_apply_patch_with_copy_and_missing(self) -> None:
         """Testing File.apply_patch with copied file and file is missing"""
         tempdir = make_tempdir()
         filename2 = os.path.join(tempdir, 'docs2', 'test2.txt')
@@ -137,10 +152,10 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_copy_and_abs_path(self):
+    def test_apply_patch_with_copy_and_abs_path(self) -> None:
         """Testing File.apply_patch with copied file and absolute path"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -160,13 +175,13 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename1, 'r') as fp:
+        with open(filename1) as fp:
             self.assertEqual(fp.read(), 'This is a test.\n')
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_delete(self):
+    def test_apply_patch_with_delete(self) -> None:
         """Testing File.apply_patch with deleted file"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -187,7 +202,7 @@
 
         self.assertFalse(os.path.exists(filename))
 
-    def test_apply_patch_with_delete_and_missing(self):
+    def test_apply_patch_with_delete_and_missing(self) -> None:
         """Testing File.apply_patch with deleted file and file missing"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -205,7 +220,7 @@
 
         self.assertFalse(os.path.exists(filename))
 
-    def test_apply_patch_with_delete_and_abs_path(self):
+    def test_apply_patch_with_delete_and_abs_path(self) -> None:
         """Testing File.apply_patch with deleted file and absolute path"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -226,7 +241,7 @@
 
         self.assertFalse(os.path.exists(filename))
 
-    def test_apply_patch_with_modified(self):
+    def test_apply_patch_with_modified(self) -> None:
         """Testing File.apply_patch with modified file"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -244,11 +259,11 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename, 'r') as fp:
+        with open(filename) as fp:
             self.assertEqual(fp.read(),
                              'This is the new content.\n')
 
-    def test_apply_patch_with_modified_and_abs_path(self):
+    def test_apply_patch_with_modified_and_abs_path(self) -> None:
         """Testing File.apply_patch with modified file and absolute path"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -266,11 +281,11 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename, 'r') as fp:
+        with open(filename) as fp:
             self.assertEqual(fp.read(),
                              'This is the new content.\n')
 
-    def test_apply_patch_with_move(self):
+    def test_apply_patch_with_move(self) -> None:
         """Testing File.apply_patch with moved file"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -292,10 +307,10 @@
 
         self.assertFalse(os.path.exists(filename1))
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_move_and_abs_path(self):
+    def test_apply_patch_with_move_and_abs_path(self) -> None:
         """Testing File.apply_patch with moved file and absolute path"""
         tempdir = make_tempdir()
         docs_dir = os.path.join(tempdir, 'docs')
@@ -317,10 +332,10 @@
 
         self.assertFalse(os.path.exists(filename1))
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_apply_patch_with_move_and_missing(self):
+    def test_apply_patch_with_move_and_missing(self) -> None:
         """Testing File.apply_patch with moved file and file is missing"""
         tempdir = make_tempdir()
         filename2 = os.path.join(tempdir, 'docs2', 'test2.txt')
@@ -333,10 +348,10 @@
             patched_content=b'This is the new content.\n')
         review_file.apply_patch(tempdir)
 
-        with open(filename2, 'r') as fp:
+        with open(filename2) as fp:
             self.assertEqual(fp.read(), 'This is the new content.\n')
 
-    def test_comment(self):
+    def test_comment(self) -> None:
         """Testing File.comment"""
         self.review_file.comment('This is a comment',
                                  first_line=12)
@@ -350,7 +365,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_line_range(self):
+    def test_comment_with_line_range(self) -> None:
         """Testing File.comment with line range"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -365,7 +380,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_line_range_exceeds_cap(self):
+    def test_comment_with_line_range_exceeds_cap(self) -> None:
         """Testing File.comment with line range > 10 lines"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -384,7 +399,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_first_line_0(self):
+    def test_comment_with_first_line_0(self) -> None:
         """Testing File.comment with first_line=0"""
         self.review_file.comment('This is a comment',
                                  first_line=0)
@@ -398,7 +413,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_first_line_none(self):
+    def test_comment_with_first_line_none(self) -> None:
         """Testing File.comment with first_line=0"""
         self.review_file.comment('This is a comment',
                                  first_line=None)
@@ -412,7 +427,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_unmodified_line_range(self):
+    def test_comment_with_unmodified_line_range(self) -> None:
         """Testing File.comment on unmodified line range"""
         self.review_file.comment('This is a comment',
                                  first_line=1,
@@ -420,7 +435,9 @@
 
         self.assertEqual(self.review.comments, [])
 
-    def test_comment_with_unmodified_line_range_with_comment_unmodified(self):
+    def test_comment_with_unmodified_line_range_with_comment_unmodified(
+        self,
+    ) -> None:
         """Testing File.comment on unmodified line range with
         comment_unmodified=True
         """
@@ -438,7 +455,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_issue_true(self):
+    def test_comment_with_issue_true(self) -> None:
         """Testing File.comment with issue=False"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -453,7 +470,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_issue_false(self):
+    def test_comment_with_issue_false(self) -> None:
         """Testing File.comment with issue=False"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -468,7 +485,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_issue_none_and_setting_true(self):
+    def test_comment_with_issue_none_and_setting_true(self) -> None:
         """Testing File.comment with issue=None and
         settings['open_issues']=True
         """
@@ -485,7 +502,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_issue_none_and_setting_false(self):
+    def test_comment_with_issue_none_and_setting_false(self) -> None:
         """Testing File.comment with issue=None and
         settings['open_issues']=False
         """
@@ -502,7 +519,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_rich_text_true(self):
+    def test_comment_with_rich_text_true(self) -> None:
         """Testing File.comment with rich_text=True"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -517,7 +534,7 @@
             'rich_text': True,
         }])
 
-    def test_comment_with_start_column(self):
+    def test_comment_with_start_column(self) -> None:
         """Testing File.comment with start_column"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -536,7 +553,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_error_code(self):
+    def test_comment_with_error_code(self) -> None:
         """Testing File.comment with error_code"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -555,7 +572,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_severity(self):
+    def test_comment_with_severity(self) -> None:
         """Testing File.comment with severity"""
         self.review_file.comment('This is a comment',
                                  first_line=12,
@@ -574,7 +591,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_text_extra(self):
+    def test_comment_with_text_extra(self) -> None:
         """Testing File.comment with text_extra"""
         self.review_file.comment(
             'This is a comment',
@@ -598,7 +615,7 @@
             'rich_text': False,
         }])
 
-    def test_comment_with_all_extra_info(self):
+    def test_comment_with_all_extra_info(self) -> None:
         """Testing File.comment with all extra information appended to text"""
         self.review_file.comment(
             'This is a comment',
@@ -628,7 +645,7 @@
             'rich_text': False,
         }])
 
-    def test_get_files_with_original_false(self):
+    def test_get_files_with_original_false(self) -> None:
         """Testing File.get_lines with original=False"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues.
@@ -664,7 +681,7 @@
             review_file.get_lines(68, 2),
             [
                 '==',
-                'if foo():'
+                'if foo():',
             ])
 
         self.assertEqual(
@@ -674,7 +691,7 @@
             review_file.get_lines(1000, 2),
             [])
 
-    def test_get_files_with_original_true(self):
+    def test_get_files_with_original_true(self) -> None:
         """Testing File.get_lines with original=False"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues.
@@ -722,7 +739,7 @@
             review_file.get_lines(1000, 2, original=True),
             [])
 
-    def test_original_file_contents(self):
+    def test_original_file_contents(self) -> None:
         """Testing File.original_file_contents"""
         review_file = self.create_review_file(
             self.review,
@@ -734,7 +751,7 @@
         self.assertEqual(review_file.original_file_contents,
                          b'This is the original content.\n')
 
-    def test_original_file_contents_with_created(self):
+    def test_original_file_contents_with_created(self) -> None:
         """Testing File.original_file_contents with status=created"""
         review_file = self.create_review_file(
             self.review,
@@ -745,7 +762,7 @@
 
         self.assertIsNone(review_file.original_file_contents)
 
-    def test_original_file_contents_without_get_original_file(self):
+    def test_original_file_contents_without_get_original_file(self) -> None:
         """Testing File.original_file_contents without original-file/ link
         in API
         """
@@ -758,7 +775,7 @@
 
         self.assertIsNone(review_file.original_file_contents)
 
-    def test_original_file_contents_with_http_404(self):
+    def test_original_file_contents_with_http_404(self) -> None:
         """Testing File.original_file_contents with HTTP 404"""
         review_file = self.create_review_file(
             self.review,
@@ -773,7 +790,7 @@
 
         self.assertIsNone(review_file.original_file_contents)
 
-    def test_original_file_contents_with_http_500(self):
+    def test_original_file_contents_with_http_500(self) -> None:
         """Testing File.original_file_contents with HTTP 500"""
         review_file = self.create_review_file(
             self.review,
@@ -796,7 +813,7 @@
             review_file._api_filediff,
             error)
 
-    def test_patched_file_contents(self):
+    def test_patched_file_contents(self) -> None:
         """Testing File.patched_file_contents"""
         review_file = self.create_review_file(
             self.review,
@@ -808,7 +825,7 @@
         self.assertEqual(review_file.patched_file_contents,
                          b'This is the patched content.\n')
 
-    def test_patched_file_contents_with_deleted(self):
+    def test_patched_file_contents_with_deleted(self) -> None:
         """Testing File.patched_file_contents with status=created"""
         review_file = self.create_review_file(
             self.review,
@@ -820,7 +837,7 @@
 
         self.assertIsNone(review_file.patched_file_contents)
 
-    def test_patched_file_contents_without_get_patched_file(self):
+    def test_patched_file_contents_without_get_patched_file(self) -> None:
         """Testing File.patched_file_contents without patched-file/ link
         in API
         """
@@ -833,7 +850,7 @@
 
         self.assertIsNone(review_file.patched_file_contents)
 
-    def test_patched_file_contents_with_http_404(self):
+    def test_patched_file_contents_with_http_404(self) -> None:
         """Testing File.patched_file_contents with HTTP 404"""
         review_file = self.create_review_file(
             self.review,
@@ -848,7 +865,7 @@
 
         self.assertIsNone(review_file.patched_file_contents)
 
-    def test_patched_file_contents_with_http_500(self):
+    def test_patched_file_contents_with_http_500(self) -> None:
         """Testing File.patched_file_contents with HTTP 500"""
         review_file = self.create_review_file(
             self.review,
@@ -871,7 +888,7 @@
             review_file._api_filediff,
             error)
 
-    def test_get_original_file_path(self):
+    def test_get_original_file_path(self) -> None:
         """Testing File.get_original_file_path"""
         review_file = self.create_review_file(
             self.review,
@@ -883,7 +900,7 @@
         self.assertEqual(review_file.get_original_file_path(),
                          os.path.join(tmpdirs[-1], 'test.txt'))
 
-    def test_get_original_file_path_with_created(self):
+    def test_get_original_file_path_with_created(self) -> None:
         """Testing File.get_original_file_path with status=created"""
         review_file = self.create_review_file(
             self.review,
@@ -894,7 +911,7 @@
 
         self.assertIsNone(review_file.get_original_file_path())
 
-    def test_get_original_file_path_with_empty_string(self):
+    def test_get_original_file_path_with_empty_string(self) -> None:
         """Testing File.get_original_file_path with content as empty string"""
         review_file = self.create_review_file(
             self.review,
@@ -906,7 +923,7 @@
         self.assertEqual(review_file.get_original_file_path(),
                          os.path.join(tmpdirs[-1], 'test.txt'))
 
-    def test_get_original_file_path_without_get_original_content(self):
+    def test_get_original_file_path_without_get_original_content(self) -> None:
         """Testing File.get_original_file_path without original-file/ link"""
         review_file = self.create_review_file(
             self.review,
@@ -917,7 +934,7 @@
 
         self.assertIsNone(review_file.get_original_file_path())
 
-    def test_get_patched_file_path(self):
+    def test_get_patched_file_path(self) -> None:
         """Testing File.get_patched_file_path"""
         review_file = self.create_review_file(
             self.review,
@@ -929,7 +946,7 @@
         self.assertEqual(review_file.get_patched_file_path(),
                          os.path.join(tmpdirs[-1], 'test.txt'))
 
-    def test_get_patched_file_path_with_deleted(self):
+    def test_get_patched_file_path_with_deleted(self) -> None:
         """Testing File.get_patched_file_path with status=deleted"""
         review_file = self.create_review_file(
             self.review,
@@ -941,7 +958,7 @@
 
         self.assertIsNone(review_file.get_patched_file_path())
 
-    def test_get_patched_file_path_with_empty_string(self):
+    def test_get_patched_file_path_with_empty_string(self) -> None:
         """Testing File.get_patched_file_path with content as empty string"""
         review_file = self.create_review_file(
             self.review,
@@ -953,7 +970,7 @@
         self.assertEqual(review_file.get_patched_file_path(),
                          os.path.join(tmpdirs[-1], 'test.txt'))
 
-    def test_get_patched_file_path_without_get_patched_content(self):
+    def test_get_patched_file_path_without_get_patched_content(self) -> None:
         """Testing File.get_patched_file_path without patched-file/ link"""
         review_file = self.create_review_file(
             self.review,
@@ -964,7 +981,7 @@
 
         self.assertIsNone(review_file.get_patched_file_path())
 
-    def test_translate_line_num_with_original_false(self):
+    def test_translate_line_num_with_original_false(self) -> None:
         """Testing File._translate_line_num with original=False"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues. We're going to check the beginning, end, and a rough
@@ -1010,10 +1027,10 @@
             self.assertEqual(
                 review_file._translate_line_num(line_num, original=False),
                 expected_vline_num,
-                'Line number %s did not map to virtual line number %s'
-                % (line_num, expected_vline_num))
+                f'Line number {line_num} did not map to virtual line number '
+                f'{expected_vline_num}')
 
-    def test_translate_line_num_with_original_true(self):
+    def test_translate_line_num_with_original_true(self) -> None:
         """Testing File._translate_line_num with original=True"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues. We're going to check the beginning, end, and a rough
@@ -1053,10 +1070,10 @@
             self.assertEqual(
                 review_file._translate_line_num(line_num, original=True),
                 expected_vline_num,
-                'Line number %s did not map to virtual line number %s'
-                % (line_num, expected_vline_num))
+                f'Line number {line_num} did not map to virtual line number '
+                f'{expected_vline_num}')
 
-    def test_is_modified_with_original_false(self):
+    def test_is_modified_with_original_false(self) -> None:
         """Testing File._is_modified with original=False"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues. We're going to test within and across chunk boundaries.
@@ -1120,10 +1137,11 @@
             self.assertIs(
                 review_file._is_modified(line_num, num_lines, original=False),
                 expected_is_modified,
-                'Modified state for line range %s-%s was expected to be %s'
-                % (line_num, line_num + num_lines, expected_is_modified))
+                f'Modified state for line range {line_num}-'
+                f'{line_num + num_lines} was expected to be '
+                f'{expected_is_modified}')
 
-    def test_is_modified_with_original_true(self):
+    def test_is_modified_with_original_true(self) -> None:
         """Testing File._is_modified with original=True"""
         # Test a bunch of ranges, to make sure we don't have any boundary
         # issues. We're going to test within and across chunk boundaries.
@@ -1178,5 +1196,6 @@
             self.assertIs(
                 review_file._is_modified(line_num, num_lines, original=True),
                 expected_is_modified,
-                'Modified state for line range %s-%s was expected to be %s'
-                % (line_num, line_num + num_lines, expected_is_modified))
+                f'Modified state for line range {line_num}-'
+                f'{line_num + num_lines} was expected to be '
+                f'{expected_is_modified}')
diff --git a/bot/reviewbot/processing/tests/test_review.py b/bot/reviewbot/processing/tests/test_review.py
index c7abebc5a9cf5760c0e1626581e03d954ccaeef3..2f3a82f94d5ad11ebf179e970e167f98adcf1894 100644
--- a/bot/reviewbot/processing/tests/test_review.py
+++ b/bot/reviewbot/processing/tests/test_review.py
@@ -14,7 +14,7 @@
 class ReviewTests(kgb.SpyAgency, TestCase):
     """Unit tests for reviewbot.processing.review.Review."""
 
-    def test_init_load_filediffs(self):
+    def test_init_load_filediffs(self) -> None:
         """Testing Review.__init__ with loading FileDiffs"""
         self.spy_on(self.api_root.get_files, op=kgb.SpyOpReturn([
             self.create_filediff_resource(
