Skip to content
Open
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
14 changes: 14 additions & 0 deletions stubs/Drupal/Component/Render/FormattableMarkup.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Drupal\Component\Render;

class FormattableMarkup implements MarkupInterface {

/**
* @param string $string
* @param array<non-empty-string, string|\Stringable> $arguments
*/
public function __construct($string, array $arguments) {
}

}
18 changes: 18 additions & 0 deletions stubs/Drupal/Core/StringTranslation/PluralTranslatableMarkup.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Drupal\Core\StringTranslation;

class PluralTranslatableMarkup extends TranslatableMarkup {

/**
* @param int $count
* @param string $singular
* @param string $plural
* @param array<non-empty-string, string|\Stringable> $args
* @param array<string, mixed> $options
* @param TranslationInterface|null $string_translation
*/
public function __construct($count, $singular, $plural, array $args = [], array $options = [], $string_translation = NULL) {
}

}
18 changes: 18 additions & 0 deletions stubs/Drupal/Core/StringTranslation/TranslatableMarkup.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Drupal\Core\StringTranslation;

use Drupal\Component\Render\FormattableMarkup;

class TranslatableMarkup extends FormattableMarkup {

/**
* @param string $string
* @param array<non-empty-string, string|\Stringable> $arguments
* @param array<string, mixed> $options
* @param TranslationInterface|null $string_translation
*/
public function __construct($string, array $arguments = [], array $options = [], $string_translation = NULL) {
}

}
31 changes: 31 additions & 0 deletions stubs/Drupal/Core/StringTranslation/TranslationInterface.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Drupal\Core\StringTranslation;

interface TranslationInterface {

/**
* @param string $string
* @param array<non-empty-string, string|\Stringable> $args
* @param array<string, mixed> $options
* @return TranslatableMarkup
*/
public function translate($string, array $args = [], array $options = []);

/**
* @param TranslatableMarkup $translated_string
* @return string
*/
public function translateString(TranslatableMarkup $translated_string);

/**
* @param int $count
* @param string $singular
* @param string $plural
* @param array<non-empty-string, string|\Stringable> $args
* @param array<string, mixed> $options
* @return PluralTranslatableMarkup
*/
public function formatPlural($count, $singular, $plural, array $args = [], array $options = []);

}
111 changes: 111 additions & 0 deletions tests/src/Rules/FormattableMarkupNullArgumentRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Rules;

use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
use PHPStan\Rules\Classes\ConsistentConstructorHelper;
use PHPStan\Rules\Classes\InstantiationRule;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\Rule;
use PHPUnit\Framework\Attributes\DataProvider;

final class FormattableMarkupNullArgumentRuleTest extends DrupalRuleTestCase
{
protected function getRule(): Rule
{
$container = self::getContainer();
/** @phpstan-ignore phpstanApi.constructor */
return new InstantiationRule(
$container,
$this->createReflectionProvider(),
/** @phpstan-ignore phpstanApi.classConstant */
$container->getByType(FunctionCallParametersCheck::class),
/** @phpstan-ignore phpstanApi.classConstant */
$container->getByType(ClassNameCheck::class),
/** @phpstan-ignore phpstanApi.classConstant */
$container->getByType(ConsistentConstructorHelper::class),
false,
);
}

/**
* @param list<array{0: string, 1: int, 2?: string|null}> $errorMessages
*/
#[DataProvider('resultData')]
public function testRule(string $path, array $errorMessages): void
{
$this->analyse([$path], $errorMessages);
}

public static function resultData(): \Generator
{
yield [
__DIR__ . '/data/formattable-markup-null-argument.php',
[
[
"Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': null} given.",
12,
],
[
"Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': null} given.",
15,
],
[
"Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': null} given.",
18,
],
[
"Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null} given.",
21,
],
[
"Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null} given.",
22,
],
[
"Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null} given.",
23,
],
[
"Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null, '@email': string} given.",
44,
],
[
"Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null, '@email': string} given.",
45,
],
[
"Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'@name': string|null, '@email': string} given.",
46,
],
[
"Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'': string} given.",
50,
],
[
"Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'': string} given.",
51,
],
[
"Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, array{'': string} given.",
52,
],
[
"Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array<non-empty-string, string|Stringable>, non-empty-array<string, string> given.",
56,
],
[
"Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, non-empty-array<string, string> given.",
57,
],
[
"Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array<non-empty-string, string|Stringable>, non-empty-array<string, string> given.",
58,
],
],
];
}
}
77 changes: 77 additions & 0 deletions tests/src/Rules/TranslationInterfaceNullArgumentRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Rules;

use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\Methods\CallMethodsRule;
use PHPStan\Rules\Methods\MethodCallCheck;
use PHPStan\Rules\Rule;
use PHPUnit\Framework\Attributes\DataProvider;

final class TranslationInterfaceNullArgumentRuleTest extends DrupalRuleTestCase
{
protected function getRule(): Rule
{
$container = self::getContainer();
/** @phpstan-ignore phpstanApi.constructor */
return new CallMethodsRule(
/** @phpstan-ignore phpstanApi.classConstant */
$container->getByType(MethodCallCheck::class),
/** @phpstan-ignore phpstanApi.classConstant */
$container->getByType(FunctionCallParametersCheck::class),
);
}

/**
* @param list<array{0: string, 1: int, 2?: string|null}> $errorMessages
*/
#[DataProvider('resultData')]
public function testRule(string $path, array $errorMessages): void
{
$this->analyse([$path], $errorMessages);
}

public static function resultData(): \Generator
{
yield [
__DIR__ . '/data/translation-interface-null-argument.php',
[
[
"Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array<non-empty-string, string|Stringable>, array{'@name': null} given.",
10,
],
[
"Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array<non-empty-string, string|Stringable>, array{'@name': null} given.",
11,
],
[
"Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array<non-empty-string, string|Stringable>, array{'@name': string|null} given.",
15,
],
[
"Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array<non-empty-string, string|Stringable>, array{'@name': string|null} given.",
16,
],
[
"Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array<non-empty-string, string|Stringable>, array{'': string} given.",
35,
],
[
"Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array<non-empty-string, string|Stringable>, array{'': string} given.",
36,
],
[
"Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array<non-empty-string, string|Stringable>, non-empty-array<string, string> given.",
40,
],
[
"Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array<non-empty-string, string|Stringable>, non-empty-array<string, string> given.",
41,
],
],
];
}
}
59 changes: 59 additions & 0 deletions tests/src/Rules/data/formattable-markup-null-argument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace FormattableMarkupNullArgTest;

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;

// Error: null literal.
new FormattableMarkup('@name is cool', ['@name' => null]);

// Error: null literal in TranslatableMarkup.
new TranslatableMarkup('@name is cool', ['@name' => null]);

// Error: null literal in PluralTranslatableMarkup (arguments at index 3).
new PluralTranslatableMarkup(5, '1 item by @name', '@count items by @name', ['@name' => null]);

function testNullableVariable(?string $name): void {
new FormattableMarkup('@name is cool', ['@name' => $name]);
new TranslatableMarkup('@name is cool', ['@name' => $name]);
new PluralTranslatableMarkup(5, '1 item by @name', '@count items by @name', ['@name' => $name]);
}

function testSafe(string $name): void {
new FormattableMarkup('@name is cool', ['@name' => $name]);
new TranslatableMarkup('@name is cool', ['@name' => $name]);
new PluralTranslatableMarkup(5, '1 item by @name', '@count items by @name', ['@name' => $name]);
}

function testNoArguments(): void {
new TranslatableMarkup('Hello world');
new PluralTranslatableMarkup(5, '1 item', '@count items');
}

function testNullCoalescing(?string $name): void {
new FormattableMarkup('@name is cool', ['@name' => $name ?? '']);
new TranslatableMarkup('@name is cool', ['@name' => $name ?? '']);
new PluralTranslatableMarkup(5, '1 item by @name', '@count items by @name', ['@name' => $name ?? '']);
}

function testMultipleArgs(?string $name, string $email): void {
new FormattableMarkup('@name (@email)', ['@name' => $name, '@email' => $email]);
new TranslatableMarkup('@name (@email)', ['@name' => $name, '@email' => $email]);
new PluralTranslatableMarkup(5, '1 item by @name (@email)', '@count items by @name (@email)', ['@name' => $name, '@email' => $email]);
}

function testEmptyKey(string $value): void {
new FormattableMarkup('test', ['' => $value]);
new TranslatableMarkup('test', ['' => $value]);
new PluralTranslatableMarkup(5, '1 test', '@count tests', ['' => $value]);
}

function testDynamicKey(string $key, string $value): void {
new FormattableMarkup('test', [$key => $value]);
new TranslatableMarkup('test', [$key => $value]);
new PluralTranslatableMarkup(5, '1 test', '@count tests', [$key => $value]);
}
42 changes: 42 additions & 0 deletions tests/src/Rules/data/translation-interface-null-argument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace TranslationInterfaceNullArgTest;

use Drupal\Core\StringTranslation\TranslationInterface;

function testNull(TranslationInterface $translation): void {
$translation->translate('@name is cool', ['@name' => null]);
$translation->formatPlural(5, '1 item by @name', '@count items by @name', ['@name' => null]);
}

function testNullable(TranslationInterface $translation, ?string $name): void {
$translation->translate('@name is cool', ['@name' => $name]);
$translation->formatPlural(5, '1 item by @name', '@count items by @name', ['@name' => $name]);
}

function testSafe(TranslationInterface $translation, string $name): void {
$translation->translate('@name is cool', ['@name' => $name]);
$translation->formatPlural(5, '1 item by @name', '@count items by @name', ['@name' => $name]);
}

function testNullCoalescing(TranslationInterface $translation, ?string $name): void {
$translation->translate('@name is cool', ['@name' => $name ?? '']);
$translation->formatPlural(5, '1 item by @name', '@count items by @name', ['@name' => $name ?? '']);
}

function testNoArguments(TranslationInterface $translation): void {
$translation->translate('Hello world');
$translation->formatPlural(5, '1 item', '@count items');
}

function testEmptyKey(TranslationInterface $translation, string $value): void {
$translation->translate('test', ['' => $value]);
$translation->formatPlural(5, '1 test', '@count tests', ['' => $value]);
}

function testDynamicKey(TranslationInterface $translation, string $key, string $value): void {
$translation->translate('test', [$key => $value]);
$translation->formatPlural(5, '1 test', '@count tests', [$key => $value]);
}
Loading