From dd907896b159b8a384847e3c9479b771b7308f90 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Wed, 24 Jul 2019 11:29:08 +0100
Subject: [PATCH] Issue #3035312 by kim.pepper, andypost, martin107,
 yogeshmpawar, naveenvalecha, pguillard, Berdir, dww, claudiu.cristea, jibran,
 Mile23: Move file_scan_directory() to file_system service

---
 core/includes/file.inc                        | 82 +++-------------
 core/includes/install.core.inc                |  8 +-
 core/includes/install.inc                     | 14 +--
 core/includes/theme.inc                       |  5 +-
 .../Core/Asset/CssCollectionOptimizer.php     |  4 +-
 .../Core/Asset/JsCollectionOptimizer.php      |  4 +-
 .../NotRegularDirectoryException.php          |  8 ++
 core/lib/Drupal/Core/File/FileSystem.php      | 94 +++++++++++++++++++
 .../Drupal/Core/File/FileSystemInterface.php  | 39 ++++++++
 .../Translator/FileTranslation.php            | 26 ++++-
 core/lib/Drupal/Core/Updater/Updater.php      |  9 +-
 .../file/tests/file_test/file_test.module     |  2 +-
 .../src/Functional/ImageAdminStylesTest.php   |  6 +-
 .../src/Functional/ImageFieldValidateTest.php |  6 +-
 .../src/Functional/ImageStyleFlushTest.php    |  6 +-
 core/modules/locale/locale.bulk.inc           |  5 +-
 core/modules/locale/locale.install            | 10 +-
 core/modules/locale/locale.translation.inc    | 12 ++-
 core/modules/media/media.install              |  2 +-
 .../InstallTranslationFilePatternTest.php     | 13 ++-
 core/modules/update/update.module             |  8 +-
 core/scripts/run-tests.sh                     |  2 +-
 .../Core/File/FileSystemDeprecationTest.php   |  7 ++
 .../Core/File/RemoteFileScanDirectoryTest.php |  3 +-
 .../Core/File/ScanDirectoryTest.php           | 64 +++++++++----
 .../Core/Installer/InstallerLanguageTest.php  |  2 +-
 .../Drupal/Tests/TestFileCreationTrait.php    |  8 +-
 sites/default/default.settings.php            |  2 +-
 28 files changed, 315 insertions(+), 136 deletions(-)
 create mode 100644 core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php
 rename core/modules/system/tests/src/{Unit => Kernel}/Installer/InstallTranslationFilePatternTest.php (83%)

diff --git a/core/includes/file.inc b/core/includes/file.inc
index 765467fd6388..bef4d253e4c2 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -992,81 +992,23 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX
  * @return
  *   An associative array (keyed on the chosen key) of objects with 'uri',
  *   'filename', and 'name' properties corresponding to the matched files.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0.
+ *   Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead.
+ *
+ * @see https://www.drupal.org/node/3038437
  */
 function file_scan_directory($dir, $mask, $options = [], $depth = 0) {
-  // Merge in defaults.
-  $options += [
-    'callback' => 0,
-    'recurse' => TRUE,
-    'key' => 'uri',
-    'min_depth' => 0,
-  ];
-  // Normalize $dir only once.
-  if ($depth == 0) {
-    /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
-    $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
-    $dir = $stream_wrapper_manager->normalizeUri($dir);
-    $dir_has_slash = (substr($dir, -1) === '/');
-  }
-
-  // Allow directories specified in settings.php to be ignored. You can use this
-  // to not check for files in common special-purpose directories. For example,
-  // node_modules and bower_components. Ignoring irrelevant directories is a
-  // performance boost.
-  if (!isset($options['nomask'])) {
-    $ignore_directories = Settings::get('file_scan_ignore_directories', []);
-    array_walk($ignore_directories, function (&$value) {
-      $value = preg_quote($value, '/');
-    });
-    $default_nomask = '/^' . implode('|', $ignore_directories) . '$/';
-  }
-
-  $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri';
+  @trigger_error('file_scan_directory() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead. See https://www.drupal.org/node/3038437', E_USER_DEPRECATED);
   $files = [];
-  // Avoid warnings when opendir does not have the permissions to open a
-  // directory.
-  if (is_dir($dir)) {
-    if ($handle = @opendir($dir)) {
-      while (FALSE !== ($filename = readdir($handle))) {
-        // Skip this file if it matches the nomask or starts with a dot.
-        if ($filename[0] != '.'
-          && !(isset($options['nomask']) && preg_match($options['nomask'], $filename))
-          && !(!empty($default_nomask) && preg_match($default_nomask, $filename))
-          ) {
-          if ($depth == 0 && $dir_has_slash) {
-            $uri = "$dir$filename";
-          }
-          else {
-            $uri = "$dir/$filename";
-          }
-          if ($options['recurse'] && is_dir($uri)) {
-            // Give priority to files in this folder by merging them in after
-            // any subdirectory files.
-            $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
-          }
-          elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
-            // Always use this match over anything already set in $files with
-            // the same $options['key'].
-            $file = new stdClass();
-            $file->uri = $uri;
-            $file->filename = $filename;
-            $file->name = pathinfo($filename, PATHINFO_FILENAME);
-            $key = $options['key'];
-            $files[$file->$key] = $file;
-            if ($options['callback']) {
-              $options['callback']($uri);
-            }
-          }
-        }
-      }
-
-      closedir($handle);
-    }
-    else {
-      \Drupal::logger('file')->error('@dir can not be opened', ['@dir' => $dir]);
+  try {
+    if (is_dir($dir)) {
+      $files = \Drupal::service('file_system')->scanDirectory($dir, $mask, $options);
     }
   }
-
+  catch (FileException $e) {
+    // Ignore and return empty array for BC.
+  }
   return $files;
 }
 
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 6910245723d5..b3b462e19ec4 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -444,7 +444,9 @@ function install_begin_request($class_loader, &$install_state) {
   else {
     $directory = $site_path . '/files/translations';
   }
-  $container->set('string_translator.file_translation', new FileTranslation($directory));
+  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+  $file_system = $container->get('file_system');
+  $container->set('string_translator.file_translation', new FileTranslation($directory, $file_system));
   $container->get('string_translation')
     ->addTranslator($container->get('string_translator.file_translation'));
 
@@ -1324,9 +1326,9 @@ function _install_select_profile(&$install_state) {
  *
  * @return
  *   An associative array of file URIs keyed by language code. URIs as
- *   returned by file_scan_directory().
+ *   returned by FileSystemInterface::scanDirectory().
  *
- * @see file_scan_directory()
+ * @see \Drupal\Core\File\FileSystemInterface::scanDirectory()
  */
 function install_find_translations() {
   $translations = [];
diff --git a/core/includes/install.inc b/core/includes/install.inc
index d07d7d66e5c9..360290430b19 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -5,15 +5,15 @@
  * API functions for installing modules and themes.
  */
 
-use Drupal\Core\Extension\Dependency;
-use Drupal\Component\Utility\Unicode;
-use Drupal\Core\File\FileSystemInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\OpCodeCache;
+use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Extension\Dependency;
 use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Site\Settings;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Requirement severity -- Informational message only.
@@ -166,9 +166,11 @@ function drupal_get_database_types() {
 
   // The internal database driver name is any valid PHP identifier.
   $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/';
-  $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
+  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+  $file_system = \Drupal::service('file_system');
+  $files = $file_system->scanDirectory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
   if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
-    $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]);
+    $files += $file_system->scanDirectory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]);
   }
   foreach ($files as $file) {
     if (file_exists($file->uri . '/Install/Tasks.php')) {
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 131e4f68ad42..5fec94c6920f 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -216,7 +216,10 @@ function drupal_find_theme_templates($cache, $extension, $path) {
   // Escape the periods in the extension.
   $regex = '/' . str_replace('.', '\.', $extension) . '$/';
   // Get a listing of all template files in the path to search.
-  $files = file_scan_directory($path, $regex, ['key' => 'filename']);
+  $files = [];
+  if (is_dir($path)) {
+    $files = \Drupal::service('file_system')->scanDirectory($path, $regex, ['key' => 'filename']);
+  }
 
   // Find templates that implement registered theme hooks and include that in
   // what is returned so that the registry knows that the theme has this
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
index 94712188bf16..26ed07f79edb 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
@@ -196,7 +196,9 @@ public function deleteAll() {
         $this->fileSystem->delete($uri);
       }
     };
-    file_scan_directory('public://css', '/.*/', ['callback' => $delete_stale]);
+    if (is_dir('public://css')) {
+      $this->fileSystem->scanDirectory('public://css', '/.*/', ['callback' => $delete_stale]);
+    }
   }
 
 }
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
index a02299ff4960..dc66652b6cea 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
@@ -198,7 +198,9 @@ public function deleteAll() {
         $this->fileSystem->delete($uri);
       }
     };
-    file_scan_directory('public://js', '/.*/', ['callback' => $delete_stale]);
+    if (is_dir('public://js')) {
+      $this->fileSystem->scanDirectory('public://js', '/.*/', ['callback' => $delete_stale]);
+    }
   }
 
 }
diff --git a/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php b/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php
new file mode 100644
index 000000000000..2f438aa4872d
--- /dev/null
+++ b/core/lib/Drupal/Core/File/Exception/NotRegularDirectoryException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Drupal\Core\File\Exception;
+
+/**
+ * Exception thrown when a target is not a regular directory (e.g. a file).
+ */
+class NotRegularDirectoryException extends FileException {}
diff --git a/core/lib/Drupal/Core/File/FileSystem.php b/core/lib/Drupal/Core/File/FileSystem.php
index a85701bd3b63..54bbe419ee02 100644
--- a/core/lib/Drupal/Core/File/FileSystem.php
+++ b/core/lib/Drupal/Core/File/FileSystem.php
@@ -8,6 +8,7 @@
 use Drupal\Core\File\Exception\FileExistsException;
 use Drupal\Core\File\Exception\FileNotExistsException;
 use Drupal\Core\File\Exception\FileWriteException;
+use Drupal\Core\File\Exception\NotRegularDirectoryException;
 use Drupal\Core\File\Exception\NotRegularFileException;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\StreamWrapperManager;
@@ -625,4 +626,97 @@ public function createFilename($basename, $directory) {
     return $destination;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function scanDirectory($dir, $mask, array $options = []) {
+    // Merge in defaults.
+    $options += [
+      'callback' => 0,
+      'recurse' => TRUE,
+      'key' => 'uri',
+      'min_depth' => 0,
+    ];
+    $dir = $this->streamWrapperManager->normalizeUri($dir);
+    if (!is_dir($dir)) {
+      throw new NotRegularDirectoryException("$dir is not a directory.");
+    }
+    // Allow directories specified in settings.php to be ignored. You can use
+    // this to not check for files in common special-purpose directories. For
+    // example, node_modules and bower_components. Ignoring irrelevant
+    // directories is a performance boost.
+    if (!isset($options['nomask'])) {
+      $ignore_directories = $this->settings->get('file_scan_ignore_directories', []);
+      array_walk($ignore_directories, function (&$value) {
+        $value = preg_quote($value, '/');
+      });
+      $options['nomask'] = '/^' . implode('|', $ignore_directories) . '$/';
+    }
+    $options['key'] = in_array($options['key'], ['uri', 'filename', 'name']) ? $options['key'] : 'uri';
+    return $this->doScanDirectory($dir, $mask, $options);
+  }
+
+  /**
+   * Internal function to handle directory scanning with recursion.
+   *
+   * @param string $dir
+   *   The base directory or URI to scan, without trailing slash.
+   * @param string $mask
+   *   The preg_match() regular expression for files to be included.
+   * @param array $options
+   *   The options as per ::scanDirectory().
+   * @param int $depth
+   *   The current depth of recursion.
+   *
+   * @return array
+   *   An associative array as per ::scanDirectory().
+   *
+   * @throws \Drupal\Core\File\Exception\NotRegularDirectoryException
+   *   If the directory does not exist.
+   *
+   * @see \Drupal\Core\File\FileSystemInterface::scanDirectory()
+   */
+  protected function doScanDirectory($dir, $mask, array $options = [], $depth = 0) {
+    $files = [];
+    // Avoid warnings when opendir does not have the permissions to open a
+    // directory.
+    if ($handle = @opendir($dir)) {
+      while (FALSE !== ($filename = readdir($handle))) {
+        // Skip this file if it matches the nomask or starts with a dot.
+        if ($filename[0] != '.' && !(preg_match($options['nomask'], $filename))) {
+          if (substr($dir, -1) == '/') {
+            $uri = "$dir$filename";
+          }
+          else {
+            $uri = "$dir/$filename";
+          }
+          if ($options['recurse'] && is_dir($uri)) {
+            // Give priority to files in this folder by merging them in after
+            // any subdirectory files.
+            $files = array_merge($this->doScanDirectory($uri, $mask, $options, $depth + 1), $files);
+          }
+          elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
+            // Always use this match over anything already set in $files with
+            // the same $options['key'].
+            $file = new \stdClass();
+            $file->uri = $uri;
+            $file->filename = $filename;
+            $file->name = pathinfo($filename, PATHINFO_FILENAME);
+            $key = $options['key'];
+            $files[$file->$key] = $file;
+            if ($options['callback']) {
+              $options['callback']($uri);
+            }
+          }
+        }
+      }
+      closedir($handle);
+    }
+    else {
+      $this->logger->error('@dir can not be opened', ['@dir' => $dir]);
+    }
+
+    return $files;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/File/FileSystemInterface.php b/core/lib/Drupal/Core/File/FileSystemInterface.php
index e8440a9a2cbb..8bcad1db3724 100644
--- a/core/lib/Drupal/Core/File/FileSystemInterface.php
+++ b/core/lib/Drupal/Core/File/FileSystemInterface.php
@@ -472,4 +472,43 @@ public function createFilename($basename, $directory);
    */
   public function getDestinationFilename($destination, $replace);
 
+  /**
+   * Finds all files that match a given mask in a given directory.
+   *
+   * Directories and files beginning with a dot are excluded; this prevents
+   * hidden files and directories (such as SVN working directories) from being
+   * scanned. Use the umask option to skip configuration directories to
+   * eliminate the possibility of accidentally exposing configuration
+   * information. Also, you can use the base directory, recurse, and min_depth
+   * options to improve performance by limiting how much of the filesystem has
+   * to be traversed.
+   *
+   * @param string $dir
+   *   The base directory or URI to scan, without trailing slash.
+   * @param string $mask
+   *   The preg_match() regular expression for files to be included.
+   * @param array $options
+   *   An associative array of additional options, with the following elements:
+   *   - 'nomask': The preg_match() regular expression for files to be excluded.
+   *     Defaults to the 'file_scan_ignore_directories' setting.
+   *   - 'callback': The callback function to call for each match. There is no
+   *     default callback.
+   *   - 'recurse': When TRUE, the directory scan will recurse the entire tree
+   *     starting at the provided directory. Defaults to TRUE.
+   *   - 'key': The key to be used for the returned associative array of files.
+   *     Possible values are 'uri', for the file's URI; 'filename', for the
+   *     basename of the file; and 'name' for the name of the file without the
+   *     extension. Defaults to 'uri'.
+   *   - 'min_depth': Minimum depth of directories to return files from.
+   *     Defaults to 0.
+   *
+   * @return array
+   *   An associative array (keyed on the chosen key) of objects with 'uri',
+   *   'filename', and 'name' properties corresponding to the matched files.
+   *
+   * @throws \Drupal\Core\File\Exception\NotRegularDirectoryException
+   *   If the directory does not exist.
+   */
+  public function scanDirectory($dir, $mask, array $options = []);
+
 }
diff --git a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
index deee5f4f0557..b096ba390c3f 100644
--- a/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
+++ b/core/lib/Drupal/Core/StringTranslation/Translator/FileTranslation.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Gettext\PoStreamReader;
 use Drupal\Component\Gettext\PoMemoryWriter;
+use Drupal\Core\File\FileSystemInterface;
 
 /**
  * File based string translation.
@@ -22,15 +23,29 @@ class FileTranslation extends StaticTranslation {
    */
   protected $directory;
 
+  /**
+   * The file system.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
   /**
    * Constructs a StaticTranslation object.
    *
    * @param string $directory
    *   The directory to retrieve file translations from.
+   * @param \Drupal\Core\File\FileSystemInterface $file_system
+   *   The file system service.
    */
-  public function __construct($directory) {
+  public function __construct($directory, FileSystemInterface $file_system = NULL) {
     parent::__construct();
     $this->directory = $directory;
+    if (!isset($file_system)) {
+      @trigger_error('Calling FileTranslation::__construct() without the $file_system argument is deprecated in drupal:8.8.0. The $file_system argument will be required in drupal:9.0.0. See https://www.drupal.org/node/3038437', E_USER_DEPRECATED);
+      $file_system = \Drupal::service('file_system');
+    }
+    $this->fileSystem = $file_system;
   }
 
   /**
@@ -65,12 +80,15 @@ protected function getLanguage($langcode) {
    *
    * @return array
    *   An associative array of file information objects keyed by file URIs as
-   *   returned by file_scan_directory().
+   *   returned by FileSystemInterface::scanDirectory().
    *
-   * @see file_scan_directory()
+   * @see \Drupal\Core\File\FileSystemInterface::scanDirectory()
    */
   public function findTranslationFiles($langcode = NULL) {
-    $files = file_scan_directory($this->directory, $this->getTranslationFilesPattern($langcode), ['recurse' => FALSE]);
+    $files = [];
+    if (is_dir($this->directory)) {
+      $files = $this->fileSystem->scanDirectory($this->directory, $this->getTranslationFilesPattern($langcode), ['recurse' => FALSE]);
+    }
     return $files;
   }
 
diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php
index ff673aa74eb3..9f63291ca12f 100644
--- a/core/lib/Drupal/Core/Updater/Updater.php
+++ b/core/lib/Drupal/Core/Updater/Updater.php
@@ -106,12 +106,17 @@ public static function getUpdaterFromDirectory($directory) {
    *   Path to the info file.
    */
   public static function findInfoFile($directory) {
-    $info_files = file_scan_directory($directory, '/.*\.info.yml$/');
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = \Drupal::service('file_system');
+    $info_files = [];
+    if (is_dir($directory)) {
+      $info_files = $file_system->scanDirectory($directory, '/.*\.info.yml$/');
+    }
     if (!$info_files) {
       return FALSE;
     }
     foreach ($info_files as $info_file) {
-      if (mb_substr($info_file->filename, 0, -9) == \Drupal::service('file_system')->basename($directory)) {
+      if (mb_substr($info_file->filename, 0, -9) == $file_system->basename($directory)) {
         // Info file Has the same name as the directory, return it.
         return $info_file->uri;
       }
diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module
index 06fe7ae99966..e31547a62686 100644
--- a/core/modules/file/tests/file_test/file_test.module
+++ b/core/modules/file/tests/file_test/file_test.module
@@ -314,7 +314,7 @@ function file_test_validator(File $file, $errors) {
 }
 
 /**
- * Helper function for testing file_scan_directory().
+ * Helper function for testing FileSystemInterface::scanDirectory().
  *
  * Each time the function is called the file is stored in a static variable.
  * When the function is called with no $filepath parameter, the results are
diff --git a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php
index eacd78869982..30b600bdf149 100644
--- a/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php
+++ b/core/modules/image/tests/src/Functional/ImageAdminStylesTest.php
@@ -44,7 +44,11 @@ public function createSampleImage(ImageStyleInterface $style) {
    * Count the number of images currently create for a style.
    */
   public function getImageCount(ImageStyleInterface $style) {
-    return count(file_scan_directory('public://styles/' . $style->id(), '/.*/'));
+    $count = 0;
+    if (is_dir('public://styles/' . $style->id())) {
+      $count = count(\Drupal::service('file_system')->scanDirectory('public://styles/' . $style->id(), '/.*/'));
+    }
+    return $count;
   }
 
   /**
diff --git a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php
index 86ad32d579d6..d7d8c3c05ef3 100644
--- a/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php
+++ b/core/modules/image/tests/src/Functional/ImageFieldValidateTest.php
@@ -40,7 +40,11 @@ public function testValid() {
     $this->drupalPostForm(NULL, [], t('Save'));
 
     // Get invalid image test files from simpletest.
-    $files = file_scan_directory(drupal_get_path('module', 'simpletest') . '/files', '/invalid-img-.*/');
+    $dir = drupal_get_path('module', 'simpletest') . '/files';
+    $files = [];
+    if (is_dir($dir)) {
+      $files = $file_system->scanDirectory($dir, '/invalid-img-.*/');
+    }
     $invalid_image_files = [];
     foreach ($files as $file) {
       $invalid_image_files[$file->filename] = $file;
diff --git a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php
index 7410fdc6b371..17490119e2ee 100644
--- a/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php
+++ b/core/modules/image/tests/src/Functional/ImageStyleFlushTest.php
@@ -42,7 +42,11 @@ public function createSampleImage($style, $wrapper) {
    * Count the number of images currently created for a style in a wrapper.
    */
   public function getImageCount($style, $wrapper) {
-    return count(file_scan_directory($wrapper . '://styles/' . $style->id(), '/.*/'));
+    $count = 0;
+    if (is_dir($wrapper . '://styles/' . $style->id())) {
+      $count = count(\Drupal::service('file_system')->scanDirectory($wrapper . '://styles/' . $style->id(), '/.*/'));
+    }
+    return $count;
   }
 
   /**
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 593ff31437ea..37c4522092cc 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -97,7 +97,10 @@ function locale_translate_get_interface_translation_files(array $projects = [],
   // {project}-{version}.{langcode}.po.
   // Only files of known projects and languages will be returned.
   $directory = \Drupal::config('locale.settings')->get('translation.path');
-  $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]);
+  $result = [];
+  if (is_dir($directory)) {
+    $result = \Drupal::service('file_system')->scanDirectory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]);
+  }
 
   foreach ($result as $file) {
     // Update the file object with project name and version from the file name.
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 13eeb2265b8a..c018be03e058 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -32,10 +32,12 @@ function locale_uninstall() {
 
   if (is_dir($locale_js_directory)) {
     $locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: [];
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = \Drupal::service('file_system');
     foreach ($locale_javascripts as $langcode => $file_suffix) {
       if (!empty($file_suffix)) {
         try {
-          \Drupal::service('file_system')->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js');
+          $file_system->delete($locale_js_directory . '/' . $langcode . '_' . $file_suffix . '.js');
         }
         catch (FileException $e) {
           // Ignore and continue.
@@ -43,8 +45,10 @@ function locale_uninstall() {
       }
     }
     // Delete the JavaScript translations directory if empty.
-    if (!file_scan_directory($locale_js_directory, '/.*/')) {
-      \Drupal::service('file_system')->rmdir($locale_js_directory);
+    if (is_dir($locale_js_directory)) {
+      if (!$file_system->scanDirectory($locale_js_directory, '/.*/')) {
+        $file_system->rmdir($locale_js_directory);
+      }
     }
   }
 
diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc
index 2ef38f0a1322..2672d5c38a56 100644
--- a/core/modules/locale/locale.translation.inc
+++ b/core/modules/locale/locale.translation.inc
@@ -175,11 +175,13 @@ function locale_translation_source_check_file($source) {
     $directory = $source_file->directory;
     $filename = '/' . preg_quote($source_file->filename) . '$/';
 
-    if ($files = file_scan_directory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) {
-      $file = current($files);
-      $source_file->uri = $file->uri;
-      $source_file->timestamp = filemtime($file->uri);
-      return $source_file;
+    if (is_dir($directory)) {
+      if ($files = \Drupal::service('file_system')->scanDirectory($directory, $filename, ['key' => 'name', 'recurse' => FALSE])) {
+        $file = current($files);
+        $source_file->uri = $file->uri;
+        $source_file->timestamp = filemtime($file->uri);
+        return $source_file;
+      }
     }
   }
   return FALSE;
diff --git a/core/modules/media/media.install b/core/modules/media/media.install
index 5bf11c2ce42e..7d8764c03ebd 100644
--- a/core/modules/media/media.install
+++ b/core/modules/media/media.install
@@ -24,7 +24,7 @@ function media_install() {
   $file_system = \Drupal::service('file_system');
   $file_system->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
 
-  $files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/');
+  $files = $file_system->scanDirectory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/');
   foreach ($files as $file) {
     // When reinstalling the media module we don't want to copy the icons when
     // they already exist. The icons could be replaced (by a contrib module or
diff --git a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php
similarity index 83%
rename from core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php
rename to core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php
index 0dd7510e89ae..b541ed8cb385 100644
--- a/core/modules/system/tests/src/Unit/Installer/InstallTranslationFilePatternTest.php
+++ b/core/modules/system/tests/src/Kernel/Installer/InstallTranslationFilePatternTest.php
@@ -1,16 +1,21 @@
 <?php
 
-namespace Drupal\Tests\system\Unit\Installer;
+namespace Drupal\Tests\system\Kernel\Installer;
 
 use Drupal\Core\StringTranslation\Translator\FileTranslation;
-use Drupal\Tests\UnitTestCase;
+use Drupal\KernelTests\KernelTestBase;
 
 /**
  * Tests for installer language support.
  *
  * @group Installer
  */
-class InstallTranslationFilePatternTest extends UnitTestCase {
+class InstallTranslationFilePatternTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system'];
 
   /**
    * @var \Drupal\Core\StringTranslation\Translator\FileTranslation
@@ -27,7 +32,7 @@ class InstallTranslationFilePatternTest extends UnitTestCase {
    */
   protected function setup() {
     parent::setUp();
-    $this->fileTranslation = new FileTranslation('filename');
+    $this->fileTranslation = new FileTranslation('filename', $this->container->get('file_system'));
     $method = new \ReflectionMethod('\Drupal\Core\StringTranslation\Translator\FileTranslation', 'getTranslationFilesPattern');
     $method->setAccessible(TRUE);
     $this->filePatternMethod = $method;
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index c783d7e82bbd..96ddedef64d1 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -690,7 +690,9 @@ function update_verify_update_archive($project, $archive_file, $directory) {
   // functionality).
   $compatible_project = FALSE;
   $incompatible = [];
-  $files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', ['key' => 'name', 'min_depth' => 0]);
+  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+  $file_system = \Drupal::service('file_system');
+  $files = $file_system->scanDirectory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', ['key' => 'name', 'min_depth' => 0]);
   foreach ($files as $file) {
     // Get the .info.yml file for the module or theme this file belongs to.
     $info = \Drupal::service('info_parser')->parse($file->uri);
@@ -705,8 +707,6 @@ function update_verify_update_archive($project, $archive_file, $directory) {
     }
   }
 
-  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
-  $file_system = \Drupal::service('file_system');
   if (empty($files)) {
     $errors[] = t('%archive_file does not contain any .info.yml files.', ['%archive_file' => $file_system->basename($archive_file)]);
   }
@@ -805,7 +805,7 @@ function update_clear_update_disk_cache() {
 
   // Search for files and directories in base folder only without recursion.
   foreach ($directories as $directory) {
-    file_scan_directory($directory, '/.*/', ['callback' => 'update_delete_file_if_stale', 'recurse' => FALSE]);
+    \Drupal::service('file_system')->scanDirectory($directory, '/.*/', ['callback' => 'update_delete_file_if_stale', 'recurse' => FALSE]);
   }
 }
 
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index f08956e61fc4..fcc3f2e602f0 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -1084,7 +1084,7 @@ function simpletest_script_get_test_list() {
       else {
         $directory = DRUPAL_ROOT . "/" . $args['directory'];
       }
-      foreach (file_scan_directory($directory, '/\.php$/', $ignore) as $file) {
+      foreach (\Drupal::service('file_system')->scanDirectory($directory, '/\.php$/', $ignore) as $file) {
         // '/Tests/' can be contained anywhere in the file's path (there can be
         // sub-directories below /Tests), but must be contained literally.
         // Case-insensitive to match all Simpletest and PHPUnit tests:
diff --git a/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php
index 58893d1fc471..c081e035f256 100644
--- a/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/File/FileSystemDeprecationTest.php
@@ -259,4 +259,11 @@ public function testRealpath() {
     $this->assertNotEmpty(drupal_realpath('public://'));
   }
 
+  /**
+   * @expectedDeprecation file_scan_directory() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\File\FileSystemInterface::scanDirectory() instead. See https://www.drupal.org/node/3038437
+   */
+  public function testDeprecatedScanDirectory() {
+    $this->assertNotNull(file_scan_directory('temporary://', '/^NONEXISTINGFILENAME/'));
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php b/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php
index e3c68a72f336..e07b1e745a73 100644
--- a/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/File/RemoteFileScanDirectoryTest.php
@@ -3,8 +3,9 @@
 namespace Drupal\KernelTests\Core\File;
 
 /**
- * Tests the file_scan_directory() function.
+ * Tests \Drupal\Core\File\FileSystemInterface::scanDirectory().
  *
+ * @coversDefaultClass \Drupal\Core\File\FileSystem
  * @group File
  */
 class RemoteFileScanDirectoryTest extends ScanDirectoryTest {
diff --git a/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php b/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php
index a096ae852677..0a0dfc1e15c0 100644
--- a/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/File/ScanDirectoryTest.php
@@ -3,8 +3,9 @@
 namespace Drupal\KernelTests\Core\File;
 
 /**
- * Tests the file_scan_directory() function.
+ * Tests \Drupal\Core\File\FileSystem::scanDirectory.
  *
+ * @coversDefaultClass \Drupal\Core\File\FileSystem
  * @group File
  */
 class ScanDirectoryTest extends FileTestBase {
@@ -21,6 +22,16 @@ class ScanDirectoryTest extends FileTestBase {
    */
   protected $path;
 
+  /**
+   * The file system.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     parent::setUp();
     // Hardcode the location of the simpletest files as it is already known
@@ -28,15 +39,18 @@ protected function setUp() {
     // location from drupal_get_filename() in a cached way.
     // @todo Remove as part of https://www.drupal.org/node/2186491
     $this->path = 'core/modules/simpletest/files';
+    $this->fileSystem = $this->container->get('file_system');
   }
 
   /**
    * Check the format of the returned values.
+   *
+   * @covers ::scanDirectory
    */
   public function testReturn() {
     // Grab a listing of all the JavaScript files and check that they're
     // passed to the callback.
-    $all_files = file_scan_directory($this->path, '/^javascript-/');
+    $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/');
     ksort($all_files);
     $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
 
@@ -57,11 +71,13 @@ public function testReturn() {
 
   /**
    * Check that the callback function is called correctly.
+   *
+   * @covers ::scanDirectory
    */
   public function testOptionCallback() {
 
     // When nothing is matched nothing should be passed to the callback.
-    $all_files = file_scan_directory($this->path, '/^NONEXISTINGFILENAME/', ['callback' => 'file_test_file_scan_callback']);
+    $all_files = $this->fileSystem->scanDirectory($this->path, '/^NONEXISTINGFILENAME/', ['callback' => 'file_test_file_scan_callback']);
     $this->assertEqual(0, count($all_files), 'No files were found.');
     $results = file_test_file_scan_callback();
     file_test_file_scan_callback_reset();
@@ -69,7 +85,7 @@ public function testOptionCallback() {
 
     // Grab a listing of all the JavaScript files and check that they're
     // passed to the callback.
-    $all_files = file_scan_directory($this->path, '/^javascript-/', ['callback' => 'file_test_file_scan_callback']);
+    $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['callback' => 'file_test_file_scan_callback']);
     $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
     $results = file_test_file_scan_callback();
     file_test_file_scan_callback_reset();
@@ -78,87 +94,97 @@ public function testOptionCallback() {
 
   /**
    * Check that the no-mask parameter is honored.
+   *
+   * @covers ::scanDirectory
    */
   public function testOptionNoMask() {
     // Grab a listing of all the JavaScript files.
-    $all_files = file_scan_directory($this->path, '/^javascript-/');
+    $all_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/');
     $this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
 
     // Now use the nomask parameter to filter out the .script file.
-    $filtered_files = file_scan_directory($this->path, '/^javascript-/', ['nomask' => '/.script$/']);
+    $filtered_files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['nomask' => '/.script$/']);
     $this->assertEqual(1, count($filtered_files), 'Filtered correctly.');
   }
 
   /**
    * Check that key parameter sets the return value's key.
+   *
+   * @covers ::scanDirectory
    */
   public function testOptionKey() {
     // "filename", for the path starting with $dir.
     $expected = [$this->path . '/javascript-1.txt', $this->path . '/javascript-2.script'];
-    $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'filepath']));
+    $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'filepath']));
     sort($actual);
     $this->assertEqual($expected, $actual, 'Returned the correct values for the filename key.');
 
     // "basename", for the basename of the file.
     $expected = ['javascript-1.txt', 'javascript-2.script'];
-    $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'filename']));
+    $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'filename']));
     sort($actual);
     $this->assertEqual($expected, $actual, 'Returned the correct values for the basename key.');
 
     // "name" for the name of the file without an extension.
     $expected = ['javascript-1', 'javascript-2'];
-    $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'name']));
+    $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'name']));
     sort($actual);
     $this->assertEqual($expected, $actual, 'Returned the correct values for the name key.');
 
     // Invalid option that should default back to "filename".
     $expected = [$this->path . '/javascript-1.txt', $this->path . '/javascript-2.script'];
-    $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', ['key' => 'INVALID']));
+    $actual = array_keys($this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['key' => 'INVALID']));
     sort($actual);
     $this->assertEqual($expected, $actual, 'An invalid key defaulted back to the default.');
   }
 
   /**
    * Check that the recurse option descends into subdirectories.
+   *
+   * @covers ::scanDirectory
    */
   public function testOptionRecurse() {
-    $files = file_scan_directory($this->path . '/..', '/^javascript-/', ['recurse' => FALSE]);
+    $files = $this->fileSystem->scanDirectory($this->path . '/..', '/^javascript-/', ['recurse' => FALSE]);
     $this->assertTrue(empty($files), "Without recursion couldn't find javascript files.");
 
-    $files = file_scan_directory($this->path . '/..', '/^javascript-/', ['recurse' => TRUE]);
+    $files = $this->fileSystem->scanDirectory($this->path . '/..', '/^javascript-/', ['recurse' => TRUE]);
     $this->assertEqual(2, count($files), 'With recursion we found the expected javascript files.');
   }
 
   /**
    * Check that the min_depth options lets us ignore files in the starting
    * directory.
+   *
+   * @covers ::scanDirectory
    */
   public function testOptionMinDepth() {
-    $files = file_scan_directory($this->path, '/^javascript-/', ['min_depth' => 0]);
+    $files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['min_depth' => 0]);
     $this->assertEqual(2, count($files), 'No minimum-depth gets files in current directory.');
 
-    $files = file_scan_directory($this->path, '/^javascript-/', ['min_depth' => 1]);
+    $files = $this->fileSystem->scanDirectory($this->path, '/^javascript-/', ['min_depth' => 1]);
     $this->assertTrue(empty($files), 'Minimum-depth of 1 successfully excludes files from current directory.');
   }
 
   /**
-   * Tests file_scan_directory() obeys 'file_scan_ignore_directories' setting.
+   * Tests ::scanDirectory obeys 'file_scan_ignore_directories' setting.
+   *
+   * @covers ::scanDirectory
    */
   public function testIgnoreDirectories() {
-    $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
+    $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
     $this->assertCount(2, $files, '2 text files found when not ignoring directories.');
 
     $this->setSetting('file_scan_ignore_directories', ['frontend_framework']);
-    $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
+    $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
     $this->assertCount(1, $files, '1 text files found when ignoring directories called "frontend_framework".');
 
     // Ensure that the directories in file_scan_ignore_directories are escaped
     // using preg_quote.
     $this->setSetting('file_scan_ignore_directories', ['frontend.*']);
-    $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
+    $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
     $this->assertCount(2, $files, '2 text files found when ignoring a directory that is not there.');
 
-    $files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/', ['nomask' => '/^something_thing_else$/']);
+    $files = $this->fileSystem->scanDirectory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/', ['nomask' => '/^something_thing_else$/']);
     $this->assertCount(2, $files, '2 text files found when an "nomask" option is passed in.');
   }
 
diff --git a/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php b/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
index e0bb6df68469..a265ed4ba033 100644
--- a/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Installer/InstallerLanguageTest.php
@@ -28,7 +28,7 @@ public function testInstallerTranslationFiles() {
 
     // Hardcode the simpletest module location as we don't yet know where it is.
     // @todo Remove as part of https://www.drupal.org/node/2186491
-    $file_translation = new FileTranslation('core/modules/simpletest/files/translations');
+    $file_translation = new FileTranslation('core/modules/simpletest/files/translations', $this->container->get('file_system'));
     foreach ($expected_translation_files as $langcode => $files_expected) {
       $files_found = $file_translation->findTranslationFiles($langcode);
       $this->assertTrue(count($files_found) == count($files_expected), new FormattableMarkup('@count installer languages found.', ['@count' => count($files_expected)]));
diff --git a/core/tests/Drupal/Tests/TestFileCreationTrait.php b/core/tests/Drupal/Tests/TestFileCreationTrait.php
index e4c699242f10..ad664e4108af 100644
--- a/core/tests/Drupal/Tests/TestFileCreationTrait.php
+++ b/core/tests/Drupal/Tests/TestFileCreationTrait.php
@@ -52,6 +52,8 @@ trait TestFileCreationTrait {
    *   List of files in public:// that match the filter(s).
    */
   protected function getTestFiles($type, $size = NULL) {
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = \Drupal::service('file_system');
     if (empty($this->generatedTestFiles)) {
       // Generate binary test files.
       $lines = [64, 1024];
@@ -69,9 +71,9 @@ protected function getTestFiles($type, $size = NULL) {
 
       // Copy other test files from simpletest.
       $original = drupal_get_path('module', 'simpletest') . '/files';
-      $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
+      $files = $file_system->scanDirectory($original, '/(html|image|javascript|php|sql)-.*/');
       foreach ($files as $file) {
-        \Drupal::service('file_system')->copy($file->uri, PublicStream::basePath());
+        $file_system->copy($file->uri, PublicStream::basePath());
       }
 
       $this->generatedTestFiles = TRUE;
@@ -80,7 +82,7 @@ protected function getTestFiles($type, $size = NULL) {
     $files = [];
     // Make sure type is valid.
     if (in_array($type, ['binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'])) {
-      $files = file_scan_directory('public://', '/' . $type . '\-.*/');
+      $files = $file_system->scanDirectory('public://', '/' . $type . '\-.*/');
 
       // If size is set then remove any files that are not of that size.
       if ($size !== NULL) {
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index d88742c677ae..e882dd138dd7 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -703,7 +703,7 @@
  * with common frontend tools and recursive scanning of directories looking for
  * extensions.
  *
- * @see file_scan_directory()
+ * @see \Drupal\Core\File\FileSystemInterface::scanDirectory()
  * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory()
  */
 $settings['file_scan_ignore_directories'] = [
-- 
GitLab