From 8b5a372c7e8e777be0e059397c428d7a92cd412d Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Mon, 9 Oct 2023 15:56:11 +0100 Subject: [PATCH] Add support for generating a baseline file (#5) * Add support for generating a baseline file * wip * wip --- README.md | 13 ++++++ config/translation-linter.php | 12 ++++++ pint.json | 3 ++ src/Collections/BaselineCollection.php | 19 +++++++++ src/Collections/ResultObjectCollection.php | 8 ++++ src/Commands/UnusedCommand.php | 23 +++++++++- .../Collections/FilterCollection.php | 2 + .../Collections/ResultObjectCollection.php | 2 + .../Readers/UnusedBaselineFileReader.php | 10 +++++ .../Writers/UnusedBaselineFileWriter.php | 10 +++++ ...IgnoreKeysFromUnusedBaselineFileFilter.php | 19 +++++++++ ...aravelTranslationLinterServiceProvider.php | 22 ++++++++++ src/Readers/UnusedBaselineFileReader.php | 32 ++++++++++++++ src/Writers/UnusedBaselineFileWriter.php | 23 ++++++++++ ...en_successfully_ignores_baseline_keys.snap | 3 ++ ...successfully_ignores_baseline_keys__2.snap | 42 +++++++++++++++++++ ...successfully_ignores_baseline_keys__3.snap | 3 ++ tests/Commands/UnusedCommandTest.php | 28 +++++++++++++ workbench/app/Example.php | 5 ++- workbench/app/ExampleJson.php | 6 ++- workbench/resources/views/welcome.blade.php | 6 +-- 21 files changed, 282 insertions(+), 9 deletions(-) create mode 100644 src/Collections/BaselineCollection.php create mode 100644 src/Contracts/Readers/UnusedBaselineFileReader.php create mode 100644 src/Contracts/Writers/UnusedBaselineFileWriter.php create mode 100644 src/Filters/IgnoreKeysFromUnusedBaselineFileFilter.php create mode 100644 src/Readers/UnusedBaselineFileReader.php create mode 100644 src/Writers/UnusedBaselineFileWriter.php create mode 100644 tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys.snap create mode 100644 tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__2.snap create mode 100644 tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__3.snap diff --git a/README.md b/README.md index 973cb4d..e588023 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,19 @@ $ php artisan translation:unused +--------+----------------------+-----------------------------------------------+ ``` +You can generate a baseline file which will be used to ignore specific keys with the +`--generate-baseline` or `-b` command options: + +```sh +$ php artisan translation:unused --generate-baseline + + INFO Baseline file written with 5 unused translation keys. + +$ php artisan translation:unused + + INFO No unused translations found! +``` + ## Roadmap - [x] Supports JSON and PHP translation files - You can enable / disable file types in the config diff --git a/config/translation-linter.php b/config/translation-linter.php index 6c37aab..c19b462 100644 --- a/config/translation-linter.php +++ b/config/translation-linter.php @@ -88,6 +88,18 @@ ], 'unused' => [ + /* + |-------------------------------------------------------------------------- + | Baseline file + |-------------------------------------------------------------------------- + | + | This is the location of the baseline file that is used to ignore specific + | translation keys. You can generate this file by using the `--generate-baseline` + | option when running the command. + | + */ + 'baseline' => base_path('translations.unused.baseline.json'), + /* |-------------------------------------------------------------------------- | Output Fields diff --git a/pint.json b/pint.json index 5deca26..fc1b78e 100644 --- a/pint.json +++ b/pint.json @@ -1,5 +1,8 @@ { "preset": "laravel", + "exclude": [ + "workbench" + ], "rules": { "single_line_empty_body": true } diff --git a/src/Collections/BaselineCollection.php b/src/Collections/BaselineCollection.php new file mode 100644 index 0000000..3e15cb9 --- /dev/null +++ b/src/Collections/BaselineCollection.php @@ -0,0 +1,19 @@ +get($locale) ?: []; + + if (in_array($key, $ignoredKeys, true)) { + return false; + } + + return true; + } +} diff --git a/src/Collections/ResultObjectCollection.php b/src/Collections/ResultObjectCollection.php index ede6936..a312eb2 100644 --- a/src/Collections/ResultObjectCollection.php +++ b/src/Collections/ResultObjectCollection.php @@ -16,6 +16,14 @@ public function reset(): void $this->items = []; } + public function toBaseLineJson(): string + { + return $this + ->groupBy('locale') + ->map(fn (ResultObjectCollection $collection) => $collection->pluck('namespaceHintedKey')->values()) + ->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + public function toCommandTableOutputArray(FieldCollectionContract $fields): array { $only = $fields->enabled()->toArray(); diff --git a/src/Commands/UnusedCommand.php b/src/Commands/UnusedCommand.php index 9a2feb2..829cfd8 100644 --- a/src/Commands/UnusedCommand.php +++ b/src/Commands/UnusedCommand.php @@ -5,20 +5,39 @@ use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFieldCollection; use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFilterCollection; use Fidum\LaravelTranslationLinter\Contracts\Linters\UnusedTranslationLinter; +use Fidum\LaravelTranslationLinter\Filters\IgnoreKeysFromUnusedBaselineFileFilter; +use Fidum\LaravelTranslationLinter\Writers\UnusedBaselineFileWriter; use Illuminate\Console\Command; class UnusedCommand extends Command { - public $signature = 'translation:unused'; + public $signature = 'translation:unused + {--b|generate-baseline : Generate a baseline file from the unused keys.}'; public $description = 'Finds unused language keys.'; public function handle( + UnusedBaselineFileWriter $writer, UnusedFieldCollection $fields, UnusedFilterCollection $filters, UnusedTranslationLinter $linter, ): int { - $results = $linter->execute()->whereShouldReport($filters); + $baseline = (bool) $this->option('generate-baseline'); + $results = $linter->execute(); + + if ($baseline) { + $results = $results->whereShouldReport($filters); + + $writer->execute($results); + + $this->components->info("Baseline file written with {$results->count()} unused translation keys."); + + return self::SUCCESS; + } + + $filters->push(IgnoreKeysFromUnusedBaselineFileFilter::class); + + $results = $results->whereShouldReport($filters); if ($results->isEmpty()) { $this->components->info('No unused translations found!'); diff --git a/src/Contracts/Collections/FilterCollection.php b/src/Contracts/Collections/FilterCollection.php index 1b91d32..7f7d54b 100644 --- a/src/Contracts/Collections/FilterCollection.php +++ b/src/Contracts/Collections/FilterCollection.php @@ -8,5 +8,7 @@ interface FilterCollection extends Arrayable, Enumerable { + public function push(...$values); + public function shouldReport(ResultObject $object): bool; } diff --git a/src/Contracts/Collections/ResultObjectCollection.php b/src/Contracts/Collections/ResultObjectCollection.php index 42cbe1a..f0eb07f 100644 --- a/src/Contracts/Collections/ResultObjectCollection.php +++ b/src/Contracts/Collections/ResultObjectCollection.php @@ -16,6 +16,8 @@ interface ResultObjectCollection extends Arrayable, Enumerable { public function reset(): void; + public function toBaseLineJson(): string; + public function toCommandTableOutputArray(FieldCollectionContract $fields): array; public function whereShouldReport(FilterCollectionContract $filters): self; diff --git a/src/Contracts/Readers/UnusedBaselineFileReader.php b/src/Contracts/Readers/UnusedBaselineFileReader.php new file mode 100644 index 0000000..00cfdc3 --- /dev/null +++ b/src/Contracts/Readers/UnusedBaselineFileReader.php @@ -0,0 +1,10 @@ +reader + ->execute() + ->shouldReport($object->locale, $object->namespaceHintedKey); + } +} diff --git a/src/LaravelTranslationLinterServiceProvider.php b/src/LaravelTranslationLinterServiceProvider.php index 508eac8..95faaf4 100644 --- a/src/LaravelTranslationLinterServiceProvider.php +++ b/src/LaravelTranslationLinterServiceProvider.php @@ -18,6 +18,8 @@ use Fidum\LaravelTranslationLinter\Contracts\Parsers\ApplicationFileParser as ApplicationFileParserContract; use Fidum\LaravelTranslationLinter\Contracts\Readers\ApplicationFileReader as ApplicationFileReaderContract; use Fidum\LaravelTranslationLinter\Contracts\Readers\LanguageFileReader as LanguageFileReaderContract; +use Fidum\LaravelTranslationLinter\Contracts\Readers\UnusedBaselineFileReader as UnusedBaselineFileReaderContract; +use Fidum\LaravelTranslationLinter\Contracts\Writers\UnusedBaselineFileWriter as UnusedBaselineFileWriterContract; use Fidum\LaravelTranslationLinter\Factories\LanguageKeyFactory; use Fidum\LaravelTranslationLinter\Factories\LanguageNamespaceKeyFactory; use Fidum\LaravelTranslationLinter\Finders\ApplicationFileFinder; @@ -28,6 +30,8 @@ use Fidum\LaravelTranslationLinter\Parsers\ApplicationFileParser; use Fidum\LaravelTranslationLinter\Readers\ApplicationFileReader; use Fidum\LaravelTranslationLinter\Readers\LanguageFileReader; +use Fidum\LaravelTranslationLinter\Readers\UnusedBaselineFileReader; +use Fidum\LaravelTranslationLinter\Writers\UnusedBaselineFileWriter; use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Foundation\Application; use Spatie\LaravelPackageTools\Package; @@ -63,6 +67,10 @@ public function registeringPackage() $this->app->bind(ApplicationFileReaderContract::class, ApplicationFileReader::class); + $this->app->when(ApplicationFileParser::class) + ->needs('$functions') + ->giveConfig('translation-linter.lang.functions'); + $this->app->bind(LanguageFileFinderContract::class, LanguageFileFinder::class); $this->app->bind(LanguageFileReaderContract::class, LanguageFileReader::class); @@ -80,6 +88,18 @@ public function registeringPackage() $this->app->bind(ResultObjectCollectionContract::class, ResultObjectCollection::class); + $this->app->scoped(UnusedBaselineFileReaderContract::class, UnusedBaselineFileReader::class); + + $this->app->when(UnusedBaselineFileReader::class) + ->needs('$file') + ->giveConfig('translation-linter.unused.baseline'); + + $this->app->bind(UnusedBaselineFileWriterContract::class, UnusedBaselineFileWriter::class); + + $this->app->when(UnusedBaselineFileWriter::class) + ->needs('$file') + ->giveConfig('translation-linter.unused.baseline'); + $this->app->bind(UnusedFieldCollectionContract::class, function (Application $app) { return UnusedFieldCollection::wrap($app->make('config')->get('translation-linter.unused.fields')); }); @@ -108,6 +128,8 @@ public function provides() LanguageNamespaceFinderContract::class, LanguageNamespaceKeyFactoryContract::class, ResultObjectCollectionContract::class, + UnusedBaselineFileReaderContract::class, + UnusedBaselineFileWriterContract::class, UnusedFieldCollectionContract::class, UnusedFilterCollectionContract::class, UnusedTranslationLinterContract::class, diff --git a/src/Readers/UnusedBaselineFileReader.php b/src/Readers/UnusedBaselineFileReader.php new file mode 100644 index 0000000..15a11fa --- /dev/null +++ b/src/Readers/UnusedBaselineFileReader.php @@ -0,0 +1,32 @@ +decoded) { + return BaselineCollection::wrap($this->decoded); + } + + if ($this->filesystem->exists($this->file)) { + $contents = $this->filesystem->get($this->file); + + $this->decoded = json_decode($contents, true); + } + + return BaselineCollection::wrap($this->decoded); + } +} diff --git a/src/Writers/UnusedBaselineFileWriter.php b/src/Writers/UnusedBaselineFileWriter.php new file mode 100644 index 0000000..a97e7a8 --- /dev/null +++ b/src/Writers/UnusedBaselineFileWriter.php @@ -0,0 +1,23 @@ +filesystem->put( + $this->file, + $results->toBaseLineJson(), + ); + } +} diff --git a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys.snap b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys.snap new file mode 100644 index 0000000..d69ed13 --- /dev/null +++ b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys.snap @@ -0,0 +1,3 @@ + + INFO Baseline file written with 36 unused translation keys. + diff --git a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__2.snap b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__2.snap new file mode 100644 index 0000000..6f0a080 --- /dev/null +++ b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__2.snap @@ -0,0 +1,42 @@ +{ + "en": [ + "Unused Vendor PHP Class", + "Unused Vendor Blade File", + "Unused Vendor Vue Component", + "example::example.unused", + "example::example.blade.choice.unused", + "example::example.blade.lang.unused", + "example::example.vue.unused", + "example::folder/example.unused", + "example::folder/example.blade.choice.unused", + "example::folder/example.blade.lang.unused", + "example::folder/example.vue.unused", + "Unused PHP Class", + "Unused Blade File", + "Unused Vue Component", + "example.unused", + "example.blade.choice.unused", + "example.blade.lang.unused", + "example.vue.unused", + "folder/example.unused", + "folder/example.blade.choice.unused", + "folder/example.blade.lang.unused", + "folder/example.vue.unused" + ], + "de": [ + "Unused Vendor PHP Class", + "Unused Vendor Blade File", + "Unused Vendor Vue Component", + "Unused PHP Class", + "Unused Blade File", + "Unused Vue Component", + "example.unused", + "example.blade.choice.unused", + "example.blade.lang.unused", + "example.vue.unused", + "folder/example.unused", + "folder/example.blade.choice.unused", + "folder/example.blade.lang.unused", + "folder/example.vue.unused" + ] +} \ No newline at end of file diff --git a/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__3.snap b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__3.snap new file mode 100644 index 0000000..8178137 --- /dev/null +++ b/tests/.pest/snapshots/Commands/UnusedCommandTest/it_generates_baseline_file_then_successfully_ignores_baseline_keys__3.snap @@ -0,0 +1,3 @@ + + INFO No unused translations found! + diff --git a/tests/Commands/UnusedCommandTest.php b/tests/Commands/UnusedCommandTest.php index 3577482..4cbed77 100644 --- a/tests/Commands/UnusedCommandTest.php +++ b/tests/Commands/UnusedCommandTest.php @@ -1,10 +1,15 @@ set('translation-linter.unused.fields.namespace', true); config()->set('translation-linter.unused.filters', []); + withoutMockingConsoleOutput(); expect(artisan('translation:unused')) ->toBe(1) @@ -67,6 +73,28 @@ ->toMatchSnapshot(); }); +it('generates baseline file then successfully ignores baseline keys', function () { + config()->set('translation-linter.lang.locales', ['en', 'de']); + config()->set('translation-linter.unused.fields.namespace', true); + config()->set('translation-linter.unused.filters', [IgnoreKeysFromUnusedBaselineFileFilter::class]); + + withoutMockingConsoleOutput(); + expect(artisan('translation:unused --generate-baseline')) + ->toBe(0) + ->and(Artisan::output()) + ->toMatchSnapshot(); + + expect($file = config('translation-linter.unused.baseline')) + ->toBeReadableFile() + ->and(file_get_contents($file)) + ->toMatchSnapshot(); + + expect(artisan('translation:unused')) + ->toBe(0) + ->and(Artisan::output()) + ->toMatchSnapshot(); +}); + it('outputs success message when no unused translations found', function () { config()->set('translation-linter.lang.locales', []); withoutMockingConsoleOutput(); diff --git a/workbench/app/Example.php b/workbench/app/Example.php index 325a5db..dfc23dd 100644 --- a/workbench/app/Example.php +++ b/workbench/app/Example.php @@ -3,15 +3,16 @@ namespace Workbench\App; use Illuminate\Support\Facades\Lang; +use Illuminate\Validation\Validator; class Example { - public function handle() + public function handle(Validator $validator) { $example = __('example.used'); if (true) { - trans('folder/example.used'); + trans("folder/example.used"); trans_choice('example::example.used'); } diff --git a/workbench/app/ExampleJson.php b/workbench/app/ExampleJson.php index bc574f6..0d90c1a 100644 --- a/workbench/app/ExampleJson.php +++ b/workbench/app/ExampleJson.php @@ -2,11 +2,13 @@ namespace Workbench\App; +use Illuminate\Validation\Validator; + class ExampleJson { - public function handle() + public function handle(Validator $validator) { __('Used PHP Class'); - __('Used Vendor PHP Class'); + __("Used Vendor PHP Class"); } } diff --git a/workbench/resources/views/welcome.blade.php b/workbench/resources/views/welcome.blade.php index f407553..b694b7a 100644 --- a/workbench/resources/views/welcome.blade.php +++ b/workbench/resources/views/welcome.blade.php @@ -5,18 +5,18 @@ ) @lang('example::example.blade.lang.used') @lang( - 'example::folder/example.blade.lang.used' + "example::folder/example.blade.lang.used" ) {{ __('Used Blade File') }} -{{ __('Used Vendor Blade File') }} +{{ __("Used Vendor Blade File") }} @if(true) @choice('example.blade.choice.used', 1) @choice('folder/example.blade.choice.used', 1) @choice('example::example.blade.choice.used', 1) @choice( - 'example::folder/example.blade.choice.used', + "example::folder/example.blade.choice.used", 1, ) @endif