Skip to content
Draft
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"test": [
"@lint",
"@phpcs",
"@phpstan",
"@phpunit",
"@behat"
]
Expand Down
15 changes: 15 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
parameters:
level: 6
paths:
- src
- i18n-command.php
scanDirectories:
- vendor/wp-cli/wp-cli/php
scanFiles:
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
- tests/phpstan/scan-files.php

treatPhpDocTypesAsCertain: false

stubFiles:
- stubs/Gettext.stub
22 changes: 12 additions & 10 deletions src/AuditCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use Gettext\Translations;
use Gettext\Utils\ParsedComment;
use WP_CLI;
use WP_CLI\Path;
use WP_CLI\Utils;


/**
* Audit strings in a WordPress project.
*
Expand Down Expand Up @@ -88,8 +90,9 @@ class AuditCommand extends MakePotCommand {
* # Audit a plugin with GitHub Actions annotations format.
* $ wp i18n audit wp-content/plugins/hello-world --format=github-actions
*
* @when before_wp_load
*
* @param array<string> $args Positional arguments.
* @param array<mixed> $assoc_args Associative arguments.
* @return void
* @throws WP_CLI\ExitException
*/
public function __invoke( $args, $assoc_args ) {
Expand All @@ -98,7 +101,7 @@ public function __invoke( $args, $assoc_args ) {
WP_CLI::error( 'Not a valid source directory.' );
}

$this->slug = Utils\get_flag_value( $assoc_args, 'slug', Utils\basename( $this->source ) );
$this->slug = Utils\get_flag_value( $assoc_args, 'slug', Path::basename( $this->source ) );
$this->domain = Utils\get_flag_value( $assoc_args, 'domain', null );
$this->skip_js = Utils\get_flag_value( $assoc_args, 'skip-js', $this->skip_js );
$this->skip_php = Utils\get_flag_value( $assoc_args, 'skip-php', $this->skip_php );
Expand All @@ -108,12 +111,12 @@ public function __invoke( $args, $assoc_args ) {
$this->format = Utils\get_flag_value( $assoc_args, 'format', $this->format );
$ignore_domain = Utils\get_flag_value( $assoc_args, 'ignore-domain', false );

$include = Utils\get_flag_value( $assoc_args, 'include', [] );
$include = Utils\get_flag_value( $assoc_args, 'include', null );
if ( ! empty( $include ) ) {
$this->include = array_map( 'trim', explode( ',', $include ) );
}

$exclude = Utils\get_flag_value( $assoc_args, 'exclude', [] );
$exclude = Utils\get_flag_value( $assoc_args, 'exclude', null );
if ( ! empty( $exclude ) ) {
$this->exclude = array_map( 'trim', explode( ',', $exclude ) );
}
Expand Down Expand Up @@ -166,7 +169,7 @@ public function __invoke( $args, $assoc_args ) {
*
* Overrides parent method to suppress log messages when using non-plaintext formats.
*
* @return array
* @return array<string, array<string, mixed>>
*/
protected function get_main_file_data() {
$files = new \IteratorIterator( new \DirectoryIterator( $this->source ) );
Expand Down Expand Up @@ -378,7 +381,7 @@ protected function get_comment_text( $comment ) {
* Goes through all extracted strings to find possible mistakes.
*
* @param Translations $translations Translations object.
* @return array Array of issues found.
* @return array<int, array<string, mixed>> Array of issues found.
*/
protected function collect_audit_issues( $translations ) {
$issues = [];
Expand Down Expand Up @@ -420,8 +423,6 @@ protected function collect_audit_issues( $translations ) {
$comments = array_filter(
$comments,
function ( $comment ) {
/** @var ParsedComment|string $comment */
/** @var string $file_header */
foreach ( $this->get_file_headers( $this->project_type ) as $file_header ) {
if ( 0 === strpos( $this->get_comment_text( $comment ), $file_header ) ) {
return false;
Expand Down Expand Up @@ -517,7 +518,8 @@ function ( $comment ) {
/**
* Outputs audit results in the specified format.
*
* @param array $issues Array of issues found.
* @param array<int, array<string, mixed>> $issues Array of issues found.
* @return void
*/
protected function output_results( $issues ) {
if ( empty( $issues ) ) {
Expand Down
11 changes: 11 additions & 0 deletions src/BladeCodeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
final class BladeCodeExtractor extends BladeGettextExtractor {
use IterableCodeExtractor;

/**
* @var array<mixed>
*/
public static $options = [
'extractComments' => [ 'translators', 'Translators' ],
'constants' => [],
Expand Down Expand Up @@ -42,10 +45,18 @@ final class BladeCodeExtractor extends BladeGettextExtractor {
],
];

/**
* @var string
*/
protected static $functionsScannerClass = 'WP_CLI\I18n\PhpFunctionsScanner';

/**
* {@inheritdoc}
*
* @param string $text The text to extract strings from.
* @param Translations $translations Translations instance.
* @param array<mixed> $options Extraction options.
* @return void
*/
public static function fromString( $text, Translations $translations, array $options = [] ) {
WP_CLI::debug( "Parsing file {$options['file']}", 'make-pot' );
Expand Down
25 changes: 15 additions & 10 deletions src/BladeGettextExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,26 @@ class BladeGettextExtractor extends \Gettext\Extractors\PhpCode {
/**
* Prepares a Blade compiler/engine and returns it.
*
* @param array<string, mixed> $options Options array.
* @return BladeOne
*/
protected static function getBladeCompiler() {
protected static function getBladeCompiler( array $options = [] ) {

$cache_path = empty( $options['cachePath'] ) ? sys_get_temp_dir() : $options['cachePath'];
$blade_compiler = new BladeOne( null, $cache_path );

if ( method_exists( $blade_compiler, 'withoutComponentTags' ) ) {
$blade_compiler->withoutComponentTags();
}

return $blade_compiler;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The removal of the withoutComponentTags() call might lead to compilation errors when BladeOne encounters Blade component tags (<x-...>). Since these tags are manually handled later in the extraction process, disabling their compilation in the main engine is important to prevent the compiler from failing on unresolved components in a non-Laravel environment.

		if ( method_exists( $blade_compiler, 'withoutComponentTags' ) ) {
			$blade_compiler->withoutComponentTags();
		}

		return $blade_compiler;

}

/**
* Compiles the Blade template string into a PHP string in one step.
*
* @param string $text Blade string to be compiled to a PHP string
* @param string $text Blade string to be compiled to a PHP string
* @param array<string, mixed> $options Options array.
* @return string
*/
protected static function compileBladeToPhp( $text ) {
return static::getBladeCompiler()->compileString( $text );
protected static function compileBladeToPhp( $text, array $options = [] ) {
return static::getBladeCompiler( $options )->compileString( $text );
}

/**
Expand Down Expand Up @@ -76,11 +75,17 @@ protected static function extractComponentPropExpressions( $text ) {
/**
* {@inheritdoc}
*
* @param string $text The text to extract strings from.
* @param array<\Gettext\Translations> $translations Translations instances.
* @param array<mixed> $options Options.
* @return void
*
* Note: In the parent PhpCode class fromString() uses fromStringMultiple() (overridden here)
*/
public static function fromStringMultiple( $text, array $translations, array $options = [] ) {
$php_string = static::compileBladeToPhp( $text );
$php_string = static::compileBladeToPhp( $text, $options );

$php_string .= static::extractComponentPropExpressions( $text );
return parent::fromStringMultiple( $php_string, $translations, $options );
parent::fromStringMultiple( $php_string, $translations, $options );
}
}
12 changes: 6 additions & 6 deletions src/FileDataExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ class FileDataExtractor {
*
* @see get_file_data()
*
* @param string $file Path to the file.
* @param array $headers List of headers, in the format array('HeaderKey' => 'Header Name').
* @param string $file Path to the file.
* @param array<string, string> $headers List of headers, in the format array('HeaderKey' => 'Header Name').
*
* @return array Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
* @return array<string, array{value: string, line: int}> Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
*/
public static function get_file_data( $file, $headers ) {
// We don't need to write to the file, so just open for reading.
Expand All @@ -39,10 +39,10 @@ public static function get_file_data( $file, $headers ) {
/**
* Retrieves metadata from a string.
*
* @param string $text String to look for metadata in.
* @param array $headers List of headers.
* @param string $text String to look for metadata in.
* @param array<string, string> $headers List of headers.
*
* @return array Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
* @return array<string, array{value: string, line: int}> Array of file headers in `HeaderKey => ['value' => Header Value, 'line' => Line Number]` format.
*/
public static function get_file_data_from_string( $text, $headers ) {
foreach ( $headers as $field => $regex ) {
Expand Down
59 changes: 33 additions & 26 deletions src/IterableCodeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,42 @@
use RecursiveIteratorIterator;
use SplFileInfo;
use WP_CLI;
use WP_CLI\Utils;
use WP_CLI\Path;



trait IterableCodeExtractor {

/**
* @var string
*/
protected static $dir = '';

/**
* Extract the translations from a file.
*
* @param array|string $file_or_files A path of a file or files
* @param Translations $translations The translations instance to append the new translations.
* @param array $options {
* @param array<mixed>|string $file_or_files A path of a file or files
* @param Translations $translations The translations instance to append the new translations.
* @param array<mixed> $options {
* Optional. An array of options passed down to static::fromString()
*
* @type bool $wpExtractTemplates Extract 'Template Name' headers in theme files. Default 'false'.
* @type bool $wpExtractPatterns Extract 'Title' and 'Description' headers in pattern files. Default 'false'.
* @type array $restrictFileNames Skip all files which are not included in this array.
* @type array $restrictDirectories Skip all directories which are not included in this array.
* @type array<string> $restrictFileNames Skip all files which are not included in this array.
* @type array<string> $restrictDirectories Skip all directories which are not included in this array.
* }
* @return null
*/
public static function fromFile( $file_or_files, Translations $translations, array $options = [] ) {
foreach ( static::getFiles( $file_or_files ) as $file ) {
if ( ! empty( $options['restrictFileNames'] ) ) {
$basename = Utils\basename( $file );
$basename = Path::basename( $file );
if ( ! in_array( $basename, $options['restrictFileNames'], true ) ) {
continue;
}
}

$relative_file_path = ltrim( str_replace( static::$dir, '', Utils\normalize_path( $file ) ), '/' );
$relative_file_path = ltrim( str_replace( static::$dir, '', Path::normalize( $file ) ), '/' );

// Make sure a relative file path is added as a comment.
$options['file'] = $relative_file_path;
Expand Down Expand Up @@ -121,24 +126,26 @@ public static function fromFile( $file_or_files, Translations $translations, arr

static::fromString( $text, $translations, $options );
}

return null;
}

/**
* Extract the translations from a file.
*
* @param string $dir Root path to start the recursive traversal in.
* @param Translations $translations The translations instance to append the new translations.
* @param array $options {
* @param string $dir Root path to start the recursive traversal in.
* @param Translations $translations The translations instance to append the new translations.
* @param array<string, mixed> $options {
* Optional. An array of options passed down to static::fromString()
*
* @type bool $wpExtractTemplates Extract 'Template Name' headers in theme files. Default 'false'.
* @type array $exclude A list of path to exclude. Default [].
* @type array $extensions A list of extensions to process. Default [].
* @type array<string> $exclude A list of path to exclude. Default [].
* @type array<string> $extensions A list of extensions to process. Default [].
* }
* @return void
*/
public static function fromDirectory( $dir, Translations $translations, array $options = [] ) {
$dir = Utils\normalize_path( $dir );
$dir = Path::normalize( $dir );

static::$dir = $dir;

Expand All @@ -157,8 +164,8 @@ public static function fromDirectory( $dir, Translations $translations, array $o
/**
* Determines whether a file is valid based on given matchers.
*
* @param SplFileInfo $file File or directory.
* @param array $matchers List of files and directories to match.
* @param SplFileInfo $file File or directory.
* @param array<string> $matchers List of files and directories to match.
* @return int How strongly the file is matched.
*/
protected static function calculateMatchScore( SplFileInfo $file, array $matchers = [] ) {
Expand Down Expand Up @@ -211,8 +218,8 @@ static function ( $component ) {
/**
* Determines whether or not a directory has children that may be matched.
*
* @param SplFileInfo $dir Directory.
* @param array $matchers List of files and directories to match.
* @param SplFileInfo $dir Directory.
* @param array<string> $matchers List of files and directories to match.
* @return bool Whether or not there are any matchers for children of this directory.
*/
protected static function containsMatchingChildren( SplFileInfo $dir, array $matchers = [] ) {
Expand Down Expand Up @@ -252,12 +259,12 @@ protected static function containsMatchingChildren( SplFileInfo $dir, array $mat
/**
* Recursively gets all PHP files within a directory.
*
* @param string $dir A path of a directory.
* @param array $includes List of files and directories to include.
* @param array $excludes List of files and directories to skip.
* @param array $extensions List of filename extensions to process.
* @param string $dir A path of a directory.
* @param array<string> $includes List of files and directories to include.
* @param array<string> $excludes List of files and directories to skip.
* @param array<string> $extensions List of filename extensions to process.
*
* @return array File list.
* @return array<string> File list.
*/
public static function getFilesFromDirectory( $dir, array $includes = [], array $excludes = [], $extensions = [] ) {
$filtered_files = [];
Expand Down Expand Up @@ -308,7 +315,7 @@ static function ( $file, $key, $iterator ) use ( $includes, $excludes, $extensio
continue;
}

$filtered_files[] = Utils\normalize_path( $file->getPathname() );
$filtered_files[] = Path::normalize( $file->getPathname() );
}

sort( $filtered_files, SORT_NATURAL | SORT_FLAG_CASE );
Expand All @@ -320,8 +327,8 @@ static function ( $file, $key, $iterator ) use ( $includes, $excludes, $extensio
* Determines whether the file extension of a file matches any of the given file extensions.
* The end/last part of a multi file extension must also match (`js` of `min.js`).
*
* @param SplFileInfo $file File or directory.
* @param array $extensions List of file extensions to match.
* @param SplFileInfo $file File or directory.
* @param array<string> $extensions List of file extensions to match.
* @return bool Whether the file has a file extension that matches any of the ones in the list.
*/
protected static function file_has_file_extension( $file, $extensions ) {
Expand Down
11 changes: 8 additions & 3 deletions src/JedGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
class JedGenerator extends Jed {
/**
* {@parentDoc}.
*
* @param Translations $translations Translations instance.
* @param array<mixed> $options Options.
* @return string
*/
public static function toString( Translations $translations, array $options = [] ) {
$options += static::$options;
Expand All @@ -38,15 +42,16 @@ public static function toString( Translations $translations, array $options = []
],
];

return json_encode( $data, $options['json'] );
$result = json_encode( $data, $options['json'] );
return false === $result ? '' : $result;
}

/**
* Generates an array with all translations.
*
* @param Translations $translations
* @param Translations $translations Translations instance.
*
* @return array
* @return array<string, array<string>>
*/
public static function buildMessages( Translations $translations ) {
$plural_forms = $translations->getPluralForms();
Expand Down
Loading