Fix strict type issues

main
Jordi Boggiano 2 years ago
parent b85e0eebc1
commit 3cdca37e85
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC

@ -293,7 +293,7 @@ EOT
$dirs = iterator_to_array($finder); $dirs = iterator_to_array($finder);
unset($finder); unset($finder);
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (!$fs->removeDirectory($dir)) { if (!$fs->removeDirectory((string) $dir)) {
throw new \RuntimeException('Could not remove '.$dir); throw new \RuntimeException('Could not remove '.$dir);
} }
} }

@ -510,7 +510,7 @@ TAGSPUBKEY
$files = iterator_to_array($finder); $files = iterator_to_array($finder);
if (count($files)) { if (count($files)) {
return basename(end($files), self::OLD_INSTALL_EXT); return end($files)->getBasename(self::OLD_INSTALL_EXT);
} }
return false; return false;

@ -147,13 +147,13 @@ class Compiler
$unexpectedFiles = array(); $unexpectedFiles = array();
foreach ($finder as $file) { foreach ($finder as $file) {
if (in_array(realpath($file), $extraFiles, true)) { if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) {
unset($extraFiles[array_search(realpath($file), $extraFiles, true)]); unset($extraFiles[$index]);
} elseif (!Preg::isMatch('{([/\\\\]LICENSE|\.php)$}', $file)) { } elseif (!Preg::isMatch('{(^LICENSE$|\.php$)}', $file->getFilename())) {
$unexpectedFiles[] = (string) $file; $unexpectedFiles[] = (string) $file;
} }
if (Preg::isMatch('{\.php[\d.]*$}', $file)) { if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) {
$this->addFile($phar, $file); $this->addFile($phar, $file);
} else { } else {
$this->addFile($phar, $file, false); $this->addFile($phar, $file, false);
@ -220,10 +220,10 @@ class Compiler
private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void
{ {
$path = $this->getRelativeFilePath($file); $path = $this->getRelativeFilePath($file);
$content = file_get_contents($file); $content = file_get_contents((string) $file);
if ($strip) { if ($strip) {
$content = $this->stripWhitespace($content); $content = $this->stripWhitespace($content);
} elseif ('LICENSE' === basename($file)) { } elseif ('LICENSE' === $file->getFilename()) {
$content = "\n".$content."\n"; $content = "\n".$content."\n";
} }

@ -150,7 +150,8 @@ class Application extends BaseApplication
} }
// switch working dir // switch working dir
if ($newWorkDir = $this->getNewWorkingDir($input)) { $newWorkDir = $this->getNewWorkingDir($input);
if (null !== $newWorkDir) {
$oldWorkingDir = Platform::getCwd(true); $oldWorkingDir = Platform::getCwd(true);
chdir($newWorkDir); chdir($newWorkDir);
$this->initialWorkingDirectory = $newWorkDir; $this->initialWorkingDirectory = $newWorkDir;
@ -171,7 +172,7 @@ class Application extends BaseApplication
} }
// prompt user for dir change if no composer.json is present in current dir // prompt user for dir change if no composer.json is present in current dir
if ($io->isInteractive() && !$newWorkDir && !in_array($commandName, array('', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'), true) && !file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false) { if ($io->isInteractive() && null === $newWorkDir && !in_array($commandName, array('', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'), true) && !file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false) {
$dir = dirname(Platform::getCwd(true)); $dir = dirname(Platform::getCwd(true));
$home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/');
@ -357,12 +358,13 @@ class Application extends BaseApplication
/** /**
* @param InputInterface $input * @param InputInterface $input
* @throws \RuntimeException * @throws \RuntimeException
* @return string * @return ?string
*/ */
private function getNewWorkingDir(InputInterface $input): string private function getNewWorkingDir(InputInterface $input): ?string
{ {
$workingDir = $input->getParameterOption(array('--working-dir', '-d')); /** @var string|null $workingDir */
if (false !== $workingDir && !is_dir($workingDir)) { $workingDir = $input->getParameterOption(array('--working-dir', '-d'), null, true);
if (null !== $workingDir && !is_dir($workingDir)) {
throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.'); throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.');
} }

@ -175,7 +175,7 @@ abstract class ArchiveDownloader extends FileDownloader
} }
$contentDir = $getFolderContent($temporaryDir); $contentDir = $getFolderContent($temporaryDir);
$singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir)); $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir));
if ($renameAsOne) { if ($renameAsOne) {
// if the target $path is clear, we can rename the whole package in one go instead of looping over the contents // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents

@ -99,7 +99,7 @@ class ArchivableFilesFinder extends \FilterIterator
return true; return true;
} }
$iterator = new FilesystemIterator($current, FilesystemIterator::SKIP_DOTS); $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS);
return !$iterator->valid(); return !$iterator->valid();
} }

@ -408,6 +408,13 @@ class ArrayLoader implements LoaderInterface
*/ */
public function getBranchAlias(array $config): ?string public function getBranchAlias(array $config): ?string
{ {
if (!isset($config['version']) || !is_scalar($config['version'])) {
throw new \UnexpectedValueException('no/invalid version defined');
}
if (!is_string($config['version'])) {
$config['version'] = (string) $config['version'];
}
if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) { if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) {
return null; return null;
} }

@ -286,8 +286,8 @@ class ValidatingArrayLoader implements LoaderInterface
// check requires for exact constraints // check requires for exact constraints
($this->flags & self::CHECK_STRICT_CONSTRAINTS) ($this->flags & self::CHECK_STRICT_CONSTRAINTS)
&& 'require' === $linkType && 'require' === $linkType
&& strpos($linkConstraint, '=') === 0 && $linkConstraint instanceof Constraint && $linkConstraint->getOperator() === Constraint::STR_OP_EQ
&& $stableConstraint->versionCompare($stableConstraint, $linkConstraint, '<=') && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint)
) { ) {
$this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
} }

@ -306,7 +306,7 @@ class PlatformRepository extends ArrayRepository
} }
if (Preg::isMatch('/^libXpm Version => (?<versionId>\d+)$/im', $info, $libxpmMatches)) { if (Preg::isMatch('/^libXpm Version => (?<versionId>\d+)$/im', $info, $libxpmMatches)) {
$this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd'); $this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd');
} }
break; break;
@ -363,7 +363,7 @@ class PlatformRepository extends ArrayRepository
$info = $this->runtime->getExtensionInfo($name); $info = $this->runtime->getExtensionInfo($name);
if (Preg::isMatch('/^Vendor Version => (?<versionId>\d+)$/im', $info, $matches) && Preg::isMatch('/^Vendor Name => (?<vendor>.+)$/im', $info, $vendorMatches)) { if (Preg::isMatch('/^Vendor Version => (?<versionId>\d+)$/im', $info, $matches) && Preg::isMatch('/^Vendor Name => (?<vendor>.+)$/im', $info, $vendorMatches)) {
$this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($matches['versionId']), $vendorMatches['vendor'].' version of ldap'); $this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap');
} }
break; break;

@ -164,7 +164,7 @@ class RepositoryFactory
if ($repo['type'] === 'filesystem') { if ($repo['type'] === 'filesystem') {
$repos[$name] = new FilesystemRepository($repo['json']); $repos[$name] = new FilesystemRepository($repo['json']);
} else { } else {
$repos[$name] = $rm->createRepository($repo['type'], $repo, $index); $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index);
} }
} }

@ -105,7 +105,11 @@ class GitLabDriver extends VcsDriver
? $match['scheme'] ? $match['scheme']
: (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
; ;
$this->originUrl = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);
if (false === $origin) {
throw new \LogicException('It should not be possible to create a gitlab driver with an unparseable origin URL ('.$this->url.')');
}
$this->originUrl = $origin;
if ($protocol = $this->config->get('gitlab-protocol')) { if ($protocol = $this->config->get('gitlab-protocol')) {
// https treated as a synonym for http. // https treated as a synonym for http.

@ -184,7 +184,7 @@ class AllFunctionalTest extends TestCase
{ {
$tests = array(); $tests = array();
foreach (Finder::create()->in(__DIR__.'/Fixtures/functional')->name('*.test')->files() as $file) { foreach (Finder::create()->in(__DIR__.'/Fixtures/functional')->name('*.test')->files() as $file) {
$tests[basename($file)] = array($file->getRealPath()); $tests[$file->getFilename()] = array((string) $file);
} }
return $tests; return $tests;

@ -55,8 +55,8 @@ class ApplicationTest extends TestCase
$inputMock->expects($this->once()) $inputMock->expects($this->once())
->method('getParameterOption') ->method('getParameterOption')
->with($this->equalTo(array('--working-dir', '-d'))) ->with($this->equalTo(array('--working-dir', '-d')), $this->equalTo(null))
->will($this->returnValue(false)); ->will($this->returnValue(null));
$inputMock->expects($this->any()) $inputMock->expects($this->any())
->method('getFirstArgument') ->method('getFirstArgument')
@ -116,8 +116,8 @@ class ApplicationTest extends TestCase
$inputMock->expects($this->once()) $inputMock->expects($this->once())
->method('getParameterOption') ->method('getParameterOption')
->with($this->equalTo(array('--working-dir', '-d'))) ->with($this->equalTo(array('--working-dir', '-d')), $this->equalTo(null))
->will($this->returnValue(false)); ->will($this->returnValue(null));
$inputMock->expects($this->any()) $inputMock->expects($this->any())
->method('getFirstArgument') ->method('getFirstArgument')

@ -17,7 +17,7 @@ use Composer\Util\Filesystem;
class CacheTest extends TestCase class CacheTest extends TestCase
{ {
/** @var string[] */ /** @var array<\SplFileInfo> */
private $files; private $files;
/** @var string */ /** @var string */
private $root; private $root;

@ -190,6 +190,8 @@ class PoolBuilderTest extends TestCase
$fixturesDir = realpath(__DIR__.'/Fixtures/poolbuilder/'); $fixturesDir = realpath(__DIR__.'/Fixtures/poolbuilder/');
$tests = array(); $tests = array();
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
$file = (string) $file;
if (!Preg::isMatch('/\.test$/', $file)) { if (!Preg::isMatch('/\.test$/', $file)) {
continue; continue;
} }
@ -220,13 +222,11 @@ class PoolBuilderTest extends TestCase
} }
/** /**
* @param \SplFileInfo $file
* @param string $fixturesDir
* @return array<string, string> * @return array<string, string>
*/ */
protected function readTestFile(\SplFileInfo $file, string $fixturesDir): array protected function readTestFile(string $file, string $fixturesDir): array
{ {
$tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE); $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE);
$sectionInfo = array( $sectionInfo = array(
'TEST' => true, 'TEST' => true,

@ -76,6 +76,8 @@ class PoolOptimizerTest extends TestCase
$fixturesDir = realpath(__DIR__.'/Fixtures/pooloptimizer/'); $fixturesDir = realpath(__DIR__.'/Fixtures/pooloptimizer/');
$tests = array(); $tests = array();
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
$file = (string) $file;
if (!Preg::isMatch('/\.test$/', $file)) { if (!Preg::isMatch('/\.test$/', $file)) {
continue; continue;
} }
@ -97,12 +99,11 @@ class PoolOptimizerTest extends TestCase
} }
/** /**
* @param string $fixturesDir
* @return mixed[] * @return mixed[]
*/ */
protected function readTestFile(\SplFileInfo $file, string $fixturesDir): array protected function readTestFile(string $file, string $fixturesDir): array
{ {
$tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE); $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE);
/** @var array<string, bool> $sectionInfo */ /** @var array<string, bool> $sectionInfo */
$sectionInfo = array( $sectionInfo = array(

@ -24,6 +24,7 @@ use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException; use Composer\DependencyResolver\SolverProblemsException;
use Composer\Package\PackageInterface;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Repository\RepositorySet; use Composer\Repository\RepositorySet;
use Composer\Test\TestCase; use Composer\Test\TestCase;
@ -1059,7 +1060,7 @@ class SolverTest extends TestCase
} }
/** /**
* @param array<array<string, string>> $expected * @param array<array{job: string, package?: PackageInterface, from?: PackageInterface, to?: PackageInterface}> $expected
* @return void * @return void
*/ */
protected function checkSolverResult(array $expected): void protected function checkSolverResult(array $expected): void

@ -19,6 +19,7 @@ use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Transaction; use Composer\DependencyResolver\Transaction;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Test\TestCase; use Composer\Test\TestCase;
class TransactionTest extends TestCase class TransactionTest extends TestCase
@ -100,7 +101,7 @@ class TransactionTest extends TestCase
/** /**
* @param \Composer\DependencyResolver\Transaction $transaction * @param \Composer\DependencyResolver\Transaction $transaction
* @param array<array<string, string>> $expected * @param array<array{job: string, package?: PackageInterface, from?: PackageInterface, to?: PackageInterface}> $expected
* @return void * @return void
*/ */
protected function checkTransactionOperations(Transaction $transaction, array $expected): void protected function checkTransactionOperations(Transaction $transaction, array $expected): void

@ -62,7 +62,7 @@ class FossilDownloaderTest extends TestCase
self::expectException('InvalidArgumentException'); self::expectException('InvalidArgumentException');
$downloader = $this->getDownloaderMock(); $downloader = $this->getDownloaderMock();
$downloader->install($packageMock, '/path'); $downloader->install($packageMock, $this->workingDir . '/path');
} }
public function testInstall(): void public function testInstall(): void
@ -77,13 +77,13 @@ class FossilDownloaderTest extends TestCase
$process = $this->getProcessExecutorMock(); $process = $this->getProcessExecutorMock();
$process->expects(array( $process->expects(array(
$this->getCmd('fossil clone -- \'http://fossil.kd2.org/kd2fw/\' \'repo.fossil\''), $this->getCmd('fossil clone -- \'http://fossil.kd2.org/kd2fw/\' \''.$this->workingDir.'.fossil\''),
$this->getCmd('fossil open --nested -- \'repo.fossil\''), $this->getCmd('fossil open --nested -- \''.$this->workingDir.'.fossil\''),
$this->getCmd('fossil update -- \'trunk\''), $this->getCmd('fossil update -- \'trunk\''),
), true); ), true);
$downloader = $this->getDownloaderMock(null, null, $process); $downloader = $this->getDownloaderMock(null, null, $process);
$downloader->install($packageMock, 'repo'); $downloader->install($packageMock, $this->workingDir);
} }
public function testUpdateforPackageWithoutSourceReference(): void public function testUpdateforPackageWithoutSourceReference(): void

@ -77,12 +77,12 @@ class HgDownloaderTest extends TestCase
$process = $this->getProcessExecutorMock(); $process = $this->getProcessExecutorMock();
$process->expects(array( $process->expects(array(
$this->getCmd('hg clone -- \'https://mercurial.dev/l3l0/composer\' \'composerPath\''), $this->getCmd('hg clone -- \'https://mercurial.dev/l3l0/composer\' \''.$this->workingDir.'\''),
$this->getCmd('hg up -- \'ref\''), $this->getCmd('hg up -- \'ref\''),
), true); ), true);
$downloader = $this->getDownloaderMock(null, null, $process); $downloader = $this->getDownloaderMock(null, null, $process);
$downloader->install($packageMock, 'composerPath'); $downloader->install($packageMock, $this->workingDir);
} }
public function testUpdateforPackageWithoutSourceReference(): void public function testUpdateforPackageWithoutSourceReference(): void

@ -521,6 +521,8 @@ class InstallerTest extends TestCase
$tests = array(); $tests = array();
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
$file = (string) $file;
if (!Preg::isMatch('/\.test$/', $file)) { if (!Preg::isMatch('/\.test$/', $file)) {
continue; continue;
} }
@ -598,12 +600,11 @@ class InstallerTest extends TestCase
} }
/** /**
* @param string $fixturesDir
* @return mixed[] * @return mixed[]
*/ */
protected function readTestFile(\SplFileInfo $file, string $fixturesDir): array protected function readTestFile(string $file, string $fixturesDir): array
{ {
$tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE); $tokens = Preg::split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file), -1, PREG_SPLIT_DELIM_CAPTURE);
$sectionInfo = array( $sectionInfo = array(
'TEST' => true, 'TEST' => true,

@ -103,7 +103,7 @@ class ProcessExecutorMock extends ProcessExecutor
Assert::assertTrue(true); // @phpstan-ignore-line Assert::assertTrue(true); // @phpstan-ignore-line
} }
public function execute($command, &$output = null, $cwd = null): int public function execute($command, &$output = null, ?string $cwd = null): int
{ {
$cwd = $cwd ?? Platform::getCwd(); $cwd = $cwd ?? Platform::getCwd();
if (func_num_args() > 1) { if (func_num_args() > 1) {
@ -113,7 +113,7 @@ class ProcessExecutorMock extends ProcessExecutor
return $this->doExecute($command, $cwd, false); return $this->doExecute($command, $cwd, false);
} }
public function executeTty($command, $cwd = null): int public function executeTty($command, ?string $cwd = null): int
{ {
$cwd = $cwd ?? Platform::getCwd(); $cwd = $cwd ?? Platform::getCwd();
if (Platform::isTty()) { if (Platform::isTty()) {

@ -289,7 +289,7 @@ class ArchivableFilesFinderTest extends TestCase
$files = array(); $files = array();
foreach ($iterator as $file) { foreach ($iterator as $file) {
$files[] = Preg::replace('#^phar://'.preg_quote($this->sources, '#').'/archive\.zip/archive#', '', $this->fs->normalizePath($file)); $files[] = Preg::replace('#^phar://'.preg_quote($this->sources, '#').'/archive\.zip/archive#', '', $this->fs->normalizePath((string) $file));
} }
unset($archive, $iterator, $file); unset($archive, $iterator, $file);

@ -52,18 +52,16 @@ class GitLabDriverTest extends TestCase
public function setUp(): void public function setUp(): void
{ {
$this->home = $this->getUniqueTmpDirectory(); $this->home = $this->getUniqueTmpDirectory();
$this->config = new Config(); $this->config = $this->getConfig([
$this->config->merge(array( 'home' => $this->home,
'config' => array( 'gitlab-domains' => array(
'home' => $this->home, 'mycompany.com/gitlab',
'gitlab-domains' => array( 'gitlab.mycompany.com',
'mycompany.com/gitlab', 'othercompany.com/nested/gitlab',
'gitlab.mycompany.com', 'gitlab.com',
'othercompany.com/nested/gitlab', 'gitlab.mycompany.local',
'gitlab.com',
),
), ),
)); ]);
$this->io = $this->getMockBuilder('Composer\IO\IOInterface')->disableOriginalConstructor()->getMock(); $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->disableOriginalConstructor()->getMock();
$this->process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $this->process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
@ -593,7 +591,7 @@ JSON;
JSON; JSON;
$this->httpDownloader->expects( $this->httpDownloader->expects(
[['url' => 'https:///api/v4/projects/%2Fmyproject', 'body' => $projectData]], [['url' => 'https://gitlab.mycompany.local/api/v4/projects/mygroup%2Fmyproject', 'body' => $projectData]],
true true
); );

Loading…
Cancel
Save