diff --git a/stubs/Drupal/Component/Render/FormattableMarkup.stub b/stubs/Drupal/Component/Render/FormattableMarkup.stub new file mode 100644 index 00000000..4908d7d3 --- /dev/null +++ b/stubs/Drupal/Component/Render/FormattableMarkup.stub @@ -0,0 +1,14 @@ + $arguments + */ + public function __construct($string, array $arguments) { + } + +} diff --git a/stubs/Drupal/Core/StringTranslation/PluralTranslatableMarkup.stub b/stubs/Drupal/Core/StringTranslation/PluralTranslatableMarkup.stub new file mode 100644 index 00000000..a876917b --- /dev/null +++ b/stubs/Drupal/Core/StringTranslation/PluralTranslatableMarkup.stub @@ -0,0 +1,18 @@ + $args + * @param array $options + * @param TranslationInterface|null $string_translation + */ + public function __construct($count, $singular, $plural, array $args = [], array $options = [], $string_translation = NULL) { + } + +} diff --git a/stubs/Drupal/Core/StringTranslation/TranslatableMarkup.stub b/stubs/Drupal/Core/StringTranslation/TranslatableMarkup.stub new file mode 100644 index 00000000..6b967dcc --- /dev/null +++ b/stubs/Drupal/Core/StringTranslation/TranslatableMarkup.stub @@ -0,0 +1,18 @@ + $arguments + * @param array $options + * @param TranslationInterface|null $string_translation + */ + public function __construct($string, array $arguments = [], array $options = [], $string_translation = NULL) { + } + +} diff --git a/stubs/Drupal/Core/StringTranslation/TranslationInterface.stub b/stubs/Drupal/Core/StringTranslation/TranslationInterface.stub new file mode 100644 index 00000000..7f992425 --- /dev/null +++ b/stubs/Drupal/Core/StringTranslation/TranslationInterface.stub @@ -0,0 +1,31 @@ + $args + * @param array $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 $args + * @param array $options + * @return PluralTranslatableMarkup + */ + public function formatPlural($count, $singular, $plural, array $args = [], array $options = []); + +} diff --git a/tests/src/Rules/FormattableMarkupNullArgumentRuleTest.php b/tests/src/Rules/FormattableMarkupNullArgumentRuleTest.php new file mode 100644 index 00000000..990f7831 --- /dev/null +++ b/tests/src/Rules/FormattableMarkupNullArgumentRuleTest.php @@ -0,0 +1,111 @@ +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 $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, array{'@name': null} given.", + 12, + ], + [ + "Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array, array{'@name': null} given.", + 15, + ], + [ + "Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array, array{'@name': null} given.", + 18, + ], + [ + "Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array, array{'@name': string|null} given.", + 21, + ], + [ + "Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array, array{'@name': string|null} given.", + 22, + ], + [ + "Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array, array{'@name': string|null} given.", + 23, + ], + [ + "Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array, array{'@name': string|null, '@email': string} given.", + 44, + ], + [ + "Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array, array{'@name': string|null, '@email': string} given.", + 45, + ], + [ + "Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array, array{'@name': string|null, '@email': string} given.", + 46, + ], + [ + "Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array, array{'': string} given.", + 50, + ], + [ + "Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array, array{'': string} given.", + 51, + ], + [ + "Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array, array{'': string} given.", + 52, + ], + [ + "Parameter #2 \$arguments of class Drupal\Component\Render\FormattableMarkup constructor expects array, non-empty-array given.", + 56, + ], + [ + "Parameter #2 \$arguments of class Drupal\Core\StringTranslation\TranslatableMarkup constructor expects array, non-empty-array given.", + 57, + ], + [ + "Parameter #4 \$args of class Drupal\Core\StringTranslation\PluralTranslatableMarkup constructor expects array, non-empty-array given.", + 58, + ], + ], + ]; + } +} diff --git a/tests/src/Rules/TranslationInterfaceNullArgumentRuleTest.php b/tests/src/Rules/TranslationInterfaceNullArgumentRuleTest.php new file mode 100644 index 00000000..9de417a4 --- /dev/null +++ b/tests/src/Rules/TranslationInterfaceNullArgumentRuleTest.php @@ -0,0 +1,77 @@ +getByType(MethodCallCheck::class), + /** @phpstan-ignore phpstanApi.classConstant */ + $container->getByType(FunctionCallParametersCheck::class), + ); + } + + /** + * @param list $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, array{'@name': null} given.", + 10, + ], + [ + "Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array, array{'@name': null} given.", + 11, + ], + [ + "Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array, array{'@name': string|null} given.", + 15, + ], + [ + "Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array, array{'@name': string|null} given.", + 16, + ], + [ + "Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array, array{'': string} given.", + 35, + ], + [ + "Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array, array{'': string} given.", + 36, + ], + [ + "Parameter #2 \$args of method Drupal\Core\StringTranslation\TranslationInterface::translate() expects array, non-empty-array given.", + 40, + ], + [ + "Parameter #4 \$args of method Drupal\Core\StringTranslation\TranslationInterface::formatPlural() expects array, non-empty-array given.", + 41, + ], + ], + ]; + } +} diff --git a/tests/src/Rules/data/formattable-markup-null-argument.php b/tests/src/Rules/data/formattable-markup-null-argument.php new file mode 100644 index 00000000..89db3a58 --- /dev/null +++ b/tests/src/Rules/data/formattable-markup-null-argument.php @@ -0,0 +1,59 @@ + 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]); +} diff --git a/tests/src/Rules/data/translation-interface-null-argument.php b/tests/src/Rules/data/translation-interface-null-argument.php new file mode 100644 index 00000000..72eeddae --- /dev/null +++ b/tests/src/Rules/data/translation-interface-null-argument.php @@ -0,0 +1,42 @@ +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]); +}