Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea/
composer.lock
.phpcs.cache
.phpunit.cache
composer.lock
tests/coverage
vendor/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Composer i18n Scripts

Simplify the internationalization of your WordPress plugin or theme using WP-CLI — powered by Composer.

2 changes: 1 addition & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
parameters:
level: 8
level: 10
paths:
- src/
27 changes: 21 additions & 6 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
<!-- phpunit.xml.dist -->
<phpunit bootstrap="vendor/autoload.php"
colors="true"
stopOnFailure="false">
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnPhpunitDeprecations="true"
failOnPhpunitDeprecation="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="Unit">
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
5 changes: 5 additions & 0 deletions src/Commands/CreatePoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
class CreatePoCommand extends CustomCommand {

const DEFAULT_LANGUAGE = 'invalid_LOCALE';

/**
* Configures the command.
*/
Expand Down Expand Up @@ -54,6 +56,9 @@ protected function configure(): void {
*/
protected function command( I18nConfig $config, InputInterface $input ): string {
$locale = $input->getArgument( 'locale' );
if ( empty( $locale ) || ! is_string( $locale ) ) {
$locale = self::DEFAULT_LANGUAGE;
}
Comment on lines 58 to +61

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The condition empty( $locale ) || ! is_string( $locale ) can be simplified by using ! is_string( $locale ) alone. empty already checks for null, '', false, 0, 0.0, '0', [], so the additional empty check is redundant and makes the code less readable.

Consider removing the empty check.

if ( ! is_string( $locale ) ) {
            $locale = self::DEFAULT_LANGUAGE;
        }


return sprintf(
'if [ ! -e %1$s ]; then cp %2$s %1$s; fi',
Expand Down
5 changes: 4 additions & 1 deletion src/Commands/CustomCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ protected function execute( InputInterface $input, OutputInterface $output ): in

$output->writeln( '<info>Running: ' . $command . '</info>' );

$this->runner->exec( $command, $output_lines, $exit_code );
$output_lines = [];
$exit_code = 0;
Comment on lines +72 to +73

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Initializing $output_lines and $exit_code before calling $this->runner->exec is good for ensuring they have default values. However, these variables are passed by reference to the exec method. If exec fails to populate these variables, the code might proceed with potentially incorrect or uninitialized data. It would be safer to check if these variables are populated after the exec call and handle the error if they are not.

Consider adding a check after the exec call to ensure $output_lines and $exit_code are properly populated.


$this->runner->exec( $command, $output_lines, $exit_code );

foreach ( $output_lines as $line ) {
$output->writeln( $line );
Expand Down
6 changes: 3 additions & 3 deletions src/ShellRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class ShellRunner {
* Executes a shell command.
*
* @param string $command The command to execute.
* @param ?string[] $output The output in an array.
* @param ?int $result_code The result code of the command execution.
* @param string[] $output The output in an array.
* @param int $result_code The result code of the command execution.
Comment on lines +21 to +22

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Changing the type hints from nullable to non-nullable is a good step towards stricter type checking. However, the exec function relies on the exec PHP function, which might not always populate the $output and $result_code variables, especially in error scenarios. If the underlying exec function fails, these variables might remain uninitialized, leading to unexpected behavior in the calling code.

Consider adding error handling to check if $output and $result_code are properly populated by the exec call and throw an exception or return an error code if they are not.

* @return void
*/
public function exec( string $command, ?array &$output, ?int &$result_code ): void {
public function exec( string $command, array &$output, int &$result_code ): void {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_exec
exec( $command, $output, $result_code );
}
Expand Down
44 changes: 44 additions & 0 deletions tests/Commands/CreatePoCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Test of MakePotCommand
*/

declare(strict_types=1);

namespace Commands;

use lloc\ComposerI18nScripts\Commands\CreatePoCommand;

use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;

#[CoversClass(CreatePoCommand::class)]
class CreatePoCommandTest extends TestCase {

protected Application $application;

protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .po creation'];
$exitCode = 0;
});

$command = new CreatePoCommand($runner);

$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
}

public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:create-po' );

$this->assertSame( 'i18n:create-po', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}
44 changes: 44 additions & 0 deletions tests/Commands/MakeJsonCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Test of MakeJsonCommand
*/

declare(strict_types=1);

namespace lloc\ComposerI18nScriptsTests\Commands;

use lloc\ComposerI18nScripts\Commands\MakeJsonCommand;
use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\Console\Application;

#[CoversClass(MakeJsonCommand::class)]
class MakeJsonCommandTest extends TestCase {

protected Application $application;

protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .json generation'];
$exitCode = 0;
});

$command = new MakeJsonCommand($runner);

$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
}


public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:make-json' );

$this->assertSame( 'i18n:make-json', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}
43 changes: 43 additions & 0 deletions tests/Commands/MakeMoCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Test of MakeMoCommand
*/

declare(strict_types=1);

namespace Commands;

use lloc\ComposerI18nScripts\Commands\MakeMoCommand;
use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;

#[CoversClass(MakeMoCommand::class)]
class MakeMoCommandTest extends TestCase {

protected Application $application;

protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .mo generation'];
$exitCode = 0;
});

$command = new MakeMoCommand($runner);

$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
}

public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:make-mo' );

$this->assertSame( 'i18n:make-mo', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}
44 changes: 44 additions & 0 deletions tests/Commands/MakePhpCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Test of MakePotCommand
*/

declare(strict_types=1);

namespace Commands;

use lloc\ComposerI18nScripts\Commands\MakePhpCommand;

use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;

#[CoversClass(MakePhpCommand::class)]
class MakePhpCommandTest extends TestCase {

protected Application $application;

protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .php generation'];
$exitCode = 0;
});

$command = new MakePhpCommand($runner);

$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
}

public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:make-php' );

$this->assertSame( 'i18n:make-php', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}
40 changes: 20 additions & 20 deletions tests/Commands/MakePotCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,35 @@
use lloc\ComposerI18nScripts\Commands\MakePotCommand;

use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

#[CoversClass(MakePotCommand::class)]
class MakePotCommandTest extends TestCase {

public function testCommandIsRegisteredAndConfigured(): void {
$runner = new ShellRunner();
$application = new Application();
$application->add( new MakePotCommand($runner) );
protected Application $application;

$command = $application->find( 'i18n:make-pot' );
$this->assertSame( 'i18n:make-pot', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .pot generation'];
$exitCode = 0;
});

public function testCommandExecutionReturnsSuccess(): void {
$runner = new ShellRunner();
$application = new Application();
$application->add( new MakePotCommand($runner) );
$command = new MakePotCommand($runner);

$command = $application->find( 'i18n:make-pot' );
$tester = new CommandTester( $command );
$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
Comment on lines +30 to +34

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The $command variable is being reassigned with a new instance of MakePotCommand inside the setUp method. This is unnecessary because the $command variable is only used within the setUp method and doesn't need to be stored as a class property. This reassignment can be removed to simplify the code and reduce memory usage.

Consider removing the reassignment of the $command variable in the setUp method.

        $runner = $this->createMock(ShellRunner::class);
        $runner->method('exec')
            ->willReturnCallback(function ($cmd, &$output, &$exitCode) {
                $output = ['Mocked .pot generation'];
                $exitCode = 0;
            });

        $this->application = new Application();
        $this->application->setAutoExit(false); // Important for tests
        $this->application->add(new MakePotCommand($runner));

}

// You may need to mock or override shell exec inside the command to make this testable
$tester->execute( array() );
public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:make-pot' );

// Just test if it runs (for now)
$this->assertSame( 0, $tester->getStatusCode() );
$this->assertSame( 'i18n:make-pot', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}
43 changes: 43 additions & 0 deletions tests/Commands/UpdatePoCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Test of MakePotCommand
*/

declare(strict_types=1);

namespace Commands;

use lloc\ComposerI18nScripts\Commands\UpdatePoCommand;
use lloc\ComposerI18nScripts\ShellRunner;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;

#[CoversClass(UpdatePoCommand::class)]
class UpdatePoCommandTest extends TestCase {

protected Application $application;

protected function setUp(): void {
$runner = $this->createMock(ShellRunner::class);
$runner->method('exec')
->willReturnCallback(function ($cmd, &$output, &$exitCode) {
$output = ['Mocked .po update'];
$exitCode = 0;
});

$command = new UpdatePoCommand($runner);

$this->application = new Application();
$this->application->setAutoExit(false); // Important for tests
$this->application->add($command);
}

public function testCommandIsRegisteredAndConfigured(): void {
$command = $this->application->find( 'i18n:update-po' );

$this->assertSame( 'i18n:update-po', $command->getName() );
$this->assertNotEmpty( $command->getDescription() );
$this->assertNotEmpty( $command->getHelp() );
}
}