├── .editorconfig ├── .gitattributes ├── .gitignore ├── Makefile ├── README.md ├── app ├── Application │ └── UseCases │ │ ├── InspectHandler.php │ │ ├── InspectInput.php │ │ └── InspectOutput.php ├── Domain │ ├── Aggregators │ │ ├── FileAggregator.php │ │ └── WordAggregator.php │ ├── Entities │ │ ├── FileEntity.php │ │ └── WordEntity.php │ ├── Ports │ │ ├── AnalyzeService.php │ │ └── FileService.php │ └── ValueObjects │ │ └── Path.php ├── Infrastructure │ ├── Analyze │ │ ├── Adapters │ │ │ └── AnalyzeServiceAdapter.php │ │ ├── Mappers │ │ │ └── AnalyzeServiceMapper.php │ │ ├── Nodes │ │ │ ├── NodeExtractor.php │ │ │ └── NodeValidator.php │ │ └── Visitors │ │ │ └── ClassVisitor.php │ └── File │ │ ├── Adapters │ │ └── FileServiceAdapter.php │ │ └── Mappers │ │ └── FileFinderMapper.php ├── Presenter │ └── Commands │ │ ├── .gitkeep │ │ ├── InspectCommand.php │ │ ├── InspectCommandInput.php │ │ └── InspectCommandOutput.php └── Providers │ └── AppServiceProvider.php ├── bootstrap └── app.php ├── box.json ├── builds └── php-wording-detector ├── composer.json ├── composer.lock ├── config ├── app.php ├── commands.php └── view.php ├── php-wording-detector ├── phpunit.xml.dist ├── resources └── views │ └── inspect.blade.php ├── storage ├── app │ └── .gitignore └── framework │ └── views │ └── .gitignore └── tests ├── Builders ├── FileAggregatorBuilder.php ├── builders.php └── stubs │ └── Foo.php ├── CreatesApplication.php ├── TestCase.php └── Unit ├── Application └── InspectHandlerTest.php ├── Domain ├── Aggregators │ └── WordAggregatorTest.php └── Entities │ └── WordEntityTest.php └── Infrastructure └── Nodes ├── NodeExtractorTest.php └── NodeValidatorTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | /.github export-ignore 3 | .scrutinizer.yml export-ignore 4 | BACKERS.md export-ignore 5 | CONTRIBUTING.md export-ignore 6 | CHANGELOG.md export-ignore 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /.vscode 4 | /.vagrant 5 | .phpunit.result.cache 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | vendor/bin/phpunit 3 | 4 | cov: 5 | vendor/bin/phpunit --coverage-text | grep -A 4 "Summary:" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![testing](https://github.com/DeGraciaMathieu/php-wording-detector/actions/workflows/phpunit.yml/badge.svg)](https://github.com/DeGraciaMathieu/php-wording-detector/actions/workflows/phpunit.yml) 6 | ![Packagist Version](https://img.shields.io/packagist/v/degraciamathieu/php-wording-detector) 7 | ![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/degraciamathieu/php-wording-detector/php) 8 | 9 | # php-wording-detector 10 | 11 | Simple tool to analyze and split the words contained in your code to check your DDD approach. 12 | 13 | # Installation 14 | 15 | ``` 16 | Requires >= PHP 8.1 17 | ``` 18 | 19 | ## Phar 20 | This tool is distributed as a [PHP Archive (PHAR)](https://www.php.net/phar): 21 | 22 | ``` 23 | wget https://github.com/DeGraciaMathieu/php-wording-detector/raw/master/builds/php-wording-detector 24 | ``` 25 | 26 | ``` 27 | php php-wording-detector --version 28 | ``` 29 | 30 | ## Composer 31 | Alternately, you can directly use composer : 32 | 33 | ``` 34 | composer require degraciamathieu/php-wording-detector --dev 35 | ``` 36 | # Usage 37 | 38 | By default only variables are analyzed : 39 | 40 | ``` 41 | php php-wording-detector inspect {path} 42 | ``` 43 | 44 | The `--with-method` option allows to parse the name of the methods : 45 | 46 | ``` 47 | php php-wording-detector inspect {path} --with-method 48 | ``` 49 | 50 | ``` 51 | $ php php-wording-detector inspect app/Domains/Activity 52 | ❀ PHP Wording Detector ❀ 53 | +-------------+-----------------------+-------------+ 54 | | total words | total distincts words | average use | 55 | +-------------+-----------------------+-------------+ 56 | | 2'166 | 52 | 42 | 57 | +-------------+-----------------------+-------------+ 58 | +--------------+-------+------------+ 59 | | words | usage | percentage | 60 | +--------------+-------+------------+ 61 | | activity | 667 | 31% | 62 | | data | 154 | 7% | 63 | | code | 150 | 7% | 64 | | item | 143 | 7% | 65 | | query | 128 | 6% | 66 | | request | 88 | 4% | 67 | | mode | 85 | 4% | 68 | | translations | 78 | 4% | 69 | | id | 77 | 4% | 70 | | type | 63 | 3% | 71 | | new | 46 | 2% | 72 | | product | 41 | 2% | 73 | | translation | 41 | 2% | 74 | | types | 40 | 2% | 75 | | master | 33 | 2% | 76 | | filters | 29 | 1% | 77 | | language | 24 | 1% | 78 | | builder | 23 | 1% | 79 | | items | 22 | 1% | 80 | | section | 21 | under 1% | 81 | +--------------+-------+------------+ 82 | ``` 83 | -------------------------------------------------------------------------------- /app/Application/UseCases/InspectHandler.php: -------------------------------------------------------------------------------- 1 | hello(); 27 | 28 | $fileAggregator = $this->fileService->all( 29 | path: $input->path(), 30 | ); 31 | 32 | $wordsAggregator = $this->analyzeService->getWords( 33 | fileAggregator: $fileAggregator, 34 | withMethod: $input->withMethod(), 35 | ); 36 | 37 | $output->present($wordsAggregator); 38 | 39 | } catch (Throwable $th) { 40 | $output->error($th); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Application/UseCases/InspectInput.php: -------------------------------------------------------------------------------- 1 | files[] = $fileEntity; 14 | } 15 | 16 | public function files(): array 17 | { 18 | return $this->files; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Domain/Aggregators/WordAggregator.php: -------------------------------------------------------------------------------- 1 | canBeIgnored()) { 15 | return; 16 | } 17 | 18 | $this->increment($wordEntity); 19 | } 20 | 21 | public function sort(): void 22 | { 23 | uasort($this->words, function ($a, $b) { 24 | return $this->sortWordsByDesc($a, $b); 25 | }); 26 | } 27 | 28 | public function words(): array 29 | { 30 | return $this->words; 31 | } 32 | 33 | private function increment(WordEntity $wordEntity): void 34 | { 35 | $word = $wordEntity->value(); 36 | 37 | /** 38 | * Wow, it works well, don't judge me 39 | */ 40 | @$this->words[$word]++; 41 | } 42 | 43 | private function sortWordsByDesc($a, $b): int 44 | { 45 | if ($a == $b) { 46 | return 0; 47 | } 48 | 49 | return ($a < $b) ? 1 : -1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Domain/Entities/FileEntity.php: -------------------------------------------------------------------------------- 1 | value === 'this'; 21 | } 22 | 23 | public function value(): string 24 | { 25 | return $this->value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Domain/Ports/AnalyzeService.php: -------------------------------------------------------------------------------- 1 | value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Infrastructure/Analyze/Adapters/AnalyzeServiceAdapter.php: -------------------------------------------------------------------------------- 1 | files(); 24 | 25 | foreach ($files as $file) { 26 | $this->getFileWords($file, $withMethod); 27 | } 28 | 29 | return $this->mapper->aggregate(); 30 | } 31 | 32 | private function getFileWords(FileEntity $file, bool $withMethod) 33 | { 34 | $traverser = new NodeTraverser(); 35 | 36 | $visitor = $this->addVisitor($traverser, $withMethod); 37 | 38 | $this->traverseVisitor($traverser, $file); 39 | 40 | $this->mapper->map($visitor->words); 41 | } 42 | 43 | private function addVisitor(NodeTraverser $traverser, bool $withMethod): ClassVisitor 44 | { 45 | $visitor = new ClassVisitor($withMethod); 46 | 47 | $traverser->addVisitor($visitor); 48 | 49 | return $visitor; 50 | } 51 | 52 | private function traverseVisitor(NodeTraverser $traverser, $file): void 53 | { 54 | $contents = $file->contents; 55 | 56 | $nodes = $this->parser->parse($contents); 57 | 58 | $traverser->traverse($nodes); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Infrastructure/Analyze/Mappers/AnalyzeServiceMapper.php: -------------------------------------------------------------------------------- 1 | add($word); 19 | } 20 | } 21 | 22 | public function aggregate(): WordAggregator 23 | { 24 | return $this->wordAggregator; 25 | } 26 | 27 | private function add(string $word): void 28 | { 29 | $this->wordAggregator->add( 30 | WordEntity::from($word), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Infrastructure/Analyze/Nodes/NodeExtractor.php: -------------------------------------------------------------------------------- 1 | name; 22 | 23 | if ($name instanceof Variable) { 24 | return $name->name; 25 | } 26 | 27 | return $name; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Infrastructure/Analyze/Nodes/NodeValidator.php: -------------------------------------------------------------------------------- 1 | mustParseThisNode($node)) { 21 | 22 | $words = NodeExtractor::cutNameIntoWords($node); 23 | 24 | $this->words = array_merge($this->words, $words); 25 | } 26 | } 27 | 28 | private function mustParseThisNode(Node $node): bool 29 | { 30 | if (NodeValidator::isAVariable($node)) { 31 | return true; 32 | } 33 | 34 | return $this->withMethod 35 | ? NodeValidator::isAMethod($node) 36 | : false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Infrastructure/File/Adapters/FileServiceAdapter.php: -------------------------------------------------------------------------------- 1 | getFiles( 24 | $path->value(), 25 | ); 26 | 27 | return $this->mapper->map($files); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Infrastructure/File/Mappers/FileFinderMapper.php: -------------------------------------------------------------------------------- 1 | makeEntity($file); 21 | 22 | $this->fileAggregator->add($fileEntity); 23 | } 24 | 25 | return $this->fileAggregator; 26 | } 27 | 28 | private function makeEntity(File $file): FileEntity 29 | { 30 | return FileEntity::from([ 31 | 'fullPath' => $file->fullPath, 32 | 'displayPath' => $file->displayPath, 33 | 'contents' => $file->contents(), 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Presenter/Commands/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeGraciaMathieu/php-wording-detector/4a5b25ebeae0287beb8725b2264bdfca7e6211eb/app/Presenter/Commands/.gitkeep -------------------------------------------------------------------------------- /app/Presenter/Commands/InspectCommand.php: -------------------------------------------------------------------------------- 1 | handle( 36 | new InspectCommandInput( 37 | path: $this->argument('path'), 38 | withMethod: $this->option('with-method'), 39 | ), 40 | $inspectCommandOutput 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Presenter/Commands/InspectCommandInput.php: -------------------------------------------------------------------------------- 1 | path); 18 | } 19 | 20 | public function withMethod(): bool 21 | { 22 | return $this->withMethod; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Presenter/Commands/InspectCommandOutput.php: -------------------------------------------------------------------------------- 1 | sort(); 31 | 32 | $html = $this->makeHtml($wordAggregator); 33 | 34 | $this->renderHtml($html); 35 | } 36 | 37 | public function error(Throwable $th): void 38 | { 39 | error($th); 40 | } 41 | 42 | private function makeHtml(WordAggregator $wordAggregator): ViewContract 43 | { 44 | $words = $wordAggregator->words(); 45 | 46 | return $this->view->make('inspect', [ 47 | 'words' => $words, 48 | ]); 49 | } 50 | 51 | private function renderHtml(ViewContract $html): void 52 | { 53 | $this->htmlRenderer->render($html, OutputInterface::OUTPUT_NORMAL); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Parser::class, function ($app) { 29 | 30 | $parserFactory = new ParserFactory(); 31 | 32 | return $parserFactory->create(ParserFactory::PREFER_PHP7); 33 | }); 34 | 35 | $this->app->bind(FileService::class, FileServiceAdapter::class); 36 | $this->app->bind(AnalyzeService::class, AnalyzeServiceAdapter::class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Console\Kernel::class, 31 | LaravelZero\Framework\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Debug\ExceptionHandler::class, 36 | Illuminate\Foundation\Exceptions\Handler::class 37 | ); 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Return The Application 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This script returns the application instance. The instance is given to 45 | | the calling script so we can separate the building of the instances 46 | | from the actual running of the application and sending responses. 47 | | 48 | */ 49 | 50 | return $app; 51 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "app", 5 | "bootstrap", 6 | "config", 7 | "vendor", 8 | "resources" 9 | ], 10 | "files": [ 11 | "composer.json" 12 | ], 13 | "exclude-composer-files": false, 14 | "compression": "GZ", 15 | "compactors": [ 16 | "KevinGH\\Box\\Compactor\\Php", 17 | "KevinGH\\Box\\Compactor\\Json" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /builds/php-wording-detector: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeGraciaMathieu/php-wording-detector/4a5b25ebeae0287beb8725b2264bdfca7e6211eb/builds/php-wording-detector -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "degraciamathieu/php-wording-detector", 3 | "description": "check your DDD approach by analyzing your variables", 4 | "keywords": ["php", "quality", "ci", "metrics", "static-analyzer", "ddd"], 5 | "type": "package", 6 | "license": "MIT", 7 | "support": { 8 | "issues": "https://github.com/DeGraciaMathieu/php-wording-detector/issues", 9 | "source": "https://github.com/DeGraciaMathieu/php-wording-detector" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "De Gracia Mathieu", 14 | "email": "dev@degracia-mathieu.fr" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1", 19 | "degraciamathieu/php-file-explorer": "0.4.*", 20 | "illuminate/view": "^10.0", 21 | "laravel-zero/framework": "^10.0", 22 | "laravel/prompts": "^0.1.21", 23 | "nikic/php-parser": "^4.13", 24 | "nunomaduro/termwind": "^1.15" 25 | }, 26 | "require-dev": { 27 | "laravel/pint": "^1.5", 28 | "mockery/mockery": "^1.5.1", 29 | "phpunit/phpunit": "^10.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "App\\": "app/", 34 | "Database\\Factories\\": "database/factories/", 35 | "Database\\Seeders\\": "database/seeders/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "files": [ 40 | "./tests/Builders/builders.php" 41 | ], 42 | "psr-4": { 43 | "Tests\\": "tests/" 44 | } 45 | }, 46 | "config": { 47 | "preferred-install": "dist", 48 | "sort-packages": true, 49 | "optimize-autoloader": true, 50 | "allow-plugins": { 51 | "pestphp/pest-plugin": true 52 | } 53 | }, 54 | "minimum-stability": "stable", 55 | "prefer-stable": true, 56 | "bin": ["php-wording-detector"] 57 | } 58 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | 'Php-wording-detector', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Version 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the "version" your application is currently running 24 | | in. You may want to follow the "Semantic Versioning" - Given a version 25 | | number MAJOR.MINOR.PATCH when an update happens: https://semver.org. 26 | | 27 | */ 28 | 29 | 'version' => app('git.version'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Environment 34 | |-------------------------------------------------------------------------- 35 | | 36 | | This value determines the "environment" your application is currently 37 | | running in. This may determine how you prefer to configure various 38 | | services the application utilizes. This can be overridden using 39 | | the global command line "--env" option when calling commands. 40 | | 41 | */ 42 | 43 | 'env' => 'development', 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Application Timezone 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Here you may specify the default timezone for your application, which 51 | | will be used by the PHP date and date-time functions. We have gone 52 | | ahead and set this to a sensible default for you out of the box. 53 | | 54 | */ 55 | 56 | 'timezone' => 'UTC', 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Autoloaded Service Providers 61 | |-------------------------------------------------------------------------- 62 | | 63 | | The service providers listed here will be automatically loaded on the 64 | | request to your application. Feel free to add your own services to 65 | | this array to grant expanded functionality to your applications. 66 | | 67 | */ 68 | 69 | 'providers' => [ 70 | App\Providers\AppServiceProvider::class, 71 | ], 72 | 73 | ]; 74 | -------------------------------------------------------------------------------- /config/commands.php: -------------------------------------------------------------------------------- 1 | NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Commands Paths 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the "paths" that should be loaded by the console's 24 | | kernel. Foreach "path" present on the array provided below the kernel 25 | | will extract all "Illuminate\Console\Command" based class commands. 26 | | 27 | */ 28 | 29 | 'paths' => [app_path('Presenter/Commands')], 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Added Commands 34 | |-------------------------------------------------------------------------- 35 | | 36 | | You may want to include a single command class without having to load an 37 | | entire folder. Here you can specify which commands should be added to 38 | | your list of commands. The console's kernel will try to load them. 39 | | 40 | */ 41 | 42 | 'add' => [ 43 | // .. 44 | ], 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Hidden Commands 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Your application commands will always be visible on the application list 52 | | of commands. But you can still make them "hidden" specifying an array 53 | | of commands below. All "hidden" commands can still be run/executed. 54 | | 55 | */ 56 | 57 | 'hidden' => [ 58 | NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, 59 | Symfony\Component\Console\Command\DumpCompletionCommand::class, 60 | Symfony\Component\Console\Command\HelpCommand::class, 61 | Illuminate\Console\Scheduling\ScheduleRunCommand::class, 62 | Illuminate\Console\Scheduling\ScheduleListCommand::class, 63 | Illuminate\Console\Scheduling\ScheduleFinishCommand::class, 64 | Illuminate\Foundation\Console\VendorPublishCommand::class, 65 | LaravelZero\Framework\Commands\StubPublishCommand::class, 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Removed Commands 71 | |-------------------------------------------------------------------------- 72 | | 73 | | Do you have a service provider that loads a list of commands that 74 | | you don't need? No problem. Laravel Zero allows you to specify 75 | | below a list of commands that you don't to see in your app. 76 | | 77 | */ 78 | 79 | 'remove' => [ 80 | // .. 81 | ], 82 | 83 | ]; 84 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 5 | resource_path('views'), 6 | ], 7 | 'compiled' => \Phar::running() 8 | ? getcwd() 9 | : env('VIEW_COMPILED_PATH', realpath(storage_path('framework/views'))), 10 | ]; 11 | -------------------------------------------------------------------------------- /php-wording-detector: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/Unit 6 | 7 | 8 | 9 | 10 | 11 | ./app 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/views/inspect.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if($words) 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
total wordstotal distincts wordsaverage use
{{ number_format(array_sum($words), 0, ".", "'") }}{{ count($words) }}{{ number_format(array_sum($words) / count($words)) }}
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | @foreach(array_slice($words, 0, 20) as $name => $usage) 26 | 27 | 28 | 29 | 39 | 40 | @endforeach 41 |
wordsusagepercentage
{{ $name }}{{ $usage }} 30 | @php 31 | $percentage = $usage * 100 / array_sum($words); 32 | @endphp 33 | @if($percentage < 1) 34 | under 1% 35 | @else 36 | {{ number_format($percentage) }}% 37 | @endif 38 |
42 | @else 43 | No files found. 44 | @endif 45 |
46 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/Builders/FileAggregatorBuilder.php: -------------------------------------------------------------------------------- 1 | fileAggregator = new FileAggregator(); 15 | } 16 | 17 | public function withFile(): FileAggregatorBuilder 18 | { 19 | $this->fileAggregator->add($this->makeFileEntity()); 20 | 21 | return $this; 22 | } 23 | 24 | public function build(): FileAggregator 25 | { 26 | return $this->fileAggregator; 27 | } 28 | 29 | private function makeFileEntity(): FileEntity 30 | { 31 | return FileEntity::from([ 32 | 'fullPath' => '.', 33 | 'displayPath' => '.', 34 | 'contents' => file_get_contents(__DIR__ . '/stubs/Foo.php'), 35 | ]); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Builders/builders.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 18 | 19 | return $app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | mockFileService(), 24 | $this->mockAnalyzeService(), 25 | ); 26 | 27 | $handler->handle( 28 | $this->mockInputInterface(), 29 | $this->mockOutputInterface(), 30 | ); 31 | } 32 | 33 | private function mockFileService(): FileService 34 | { 35 | $fileService = Mockery::mock(FileService::class); 36 | 37 | $fileService->expects('all')->andReturn($this->makeFileAggregator()); 38 | 39 | return $fileService; 40 | } 41 | 42 | private function makeFileAggregator(): FileAggregator 43 | { 44 | return fileAggregator()->withFile()->build(); 45 | } 46 | 47 | private function mockAnalyzeService(): AnalyzeService 48 | { 49 | return Mockery::mock(AnalyzeService::class)->shouldIgnoreMissing(); 50 | } 51 | 52 | private function mockInputInterface(): InspectInput 53 | { 54 | $input = Mockery::mock(InspectInput::class); 55 | 56 | $input->expects('path')->andReturn(Path::from('.')); 57 | $input->expects('withMethod')->andReturn(false); 58 | 59 | return $input; 60 | } 61 | 62 | private function mockOutputInterface(): InspectOutput 63 | { 64 | $output = Mockery::mock(InspectOutput::class)->shouldIgnoreMissing(); 65 | 66 | $output->expects('error') 67 | ->never() 68 | ->withAnyArgs() 69 | ->andReturnUsing(function ($th) { 70 | $this->fail('Unexpected call to error with exception: ' . $th->getMessage()); 71 | }); 72 | 73 | $output->expects('present') 74 | ->withArgs(function ($argument) { 75 | return $argument instanceof WordAggregator; 76 | }); 77 | 78 | return $output; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Unit/Domain/Aggregators/WordAggregatorTest.php: -------------------------------------------------------------------------------- 1 | add(WordEntity::from('a')); 18 | $aggregator->add(WordEntity::from('b')); 19 | $aggregator->add(WordEntity::from('b')); 20 | 21 | $this->assertEquals([ 22 | 'a' => 1, 23 | 'b' => 2, 24 | ], $aggregator->words()); 25 | } 26 | 27 | #[Test] 28 | public function it_can_sort_words(): void 29 | { 30 | $aggregator = new WordAggregator(); 31 | 32 | $aggregator->add(WordEntity::from('a')); 33 | $aggregator->add(WordEntity::from('b')); 34 | $aggregator->add(WordEntity::from('b')); 35 | 36 | $aggregator->sort(); 37 | 38 | $this->assertEquals([ 39 | 'b' => 2, 40 | 'a' => 1, 41 | ], $aggregator->words()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Domain/Entities/WordEntityTest.php: -------------------------------------------------------------------------------- 1 | assertSame($word->value(), 'foo'); 18 | } 19 | 20 | #[Test] 21 | public function it_accepts_common_words(): void 22 | { 23 | $word = WordEntity::from('Foo'); 24 | 25 | $this->assertFalse($word->canBeIgnored()); 26 | } 27 | 28 | 29 | #[Test] 30 | #[DataProvider('words')] 31 | public function it_can_ignore_some_words(string $word): void 32 | { 33 | $word = WordEntity::from($word); 34 | 35 | $this->assertTrue($word->canBeIgnored()); 36 | } 37 | 38 | public static function words(): array 39 | { 40 | return [ 41 | ['this'], 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Unit/Infrastructure/Nodes/NodeExtractorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expectedWords, $words); 26 | } 27 | 28 | /** 29 | * @test 30 | * @dataProvider nameProvider 31 | */ 32 | public function it_can_cut_method_into_words(string $name, array $expectedWords): void 33 | { 34 | $method = new ClassMethod( 35 | name: $name, 36 | ); 37 | 38 | $words = NodeExtractor::cutNameIntoWords($method); 39 | 40 | $this->assertEquals($expectedWords, $words); 41 | } 42 | 43 | public static function nameProvider(): array 44 | { 45 | return [ 46 | ['getAllFoo', ['get', 'All', 'Foo']], 47 | ['get_all_foo', ['get', 'all', 'foo']], 48 | ['Get_All_Foo', ['Get', 'All', 'Foo']], 49 | ['get_All_Foo', ['get', 'All', 'Foo']], 50 | ['getallfoo', ['getallfoo']], 51 | ['get', ['get']], 52 | ]; 53 | } 54 | 55 | /** 56 | * @test 57 | */ 58 | public function it_can_get_the_name_even_if_its_a_variable(): void 59 | { 60 | $variable = new Variable( 61 | name: new Variable( 62 | name: 'getAllFoo', 63 | ), 64 | ); 65 | 66 | $words = NodeExtractor::cutNameIntoWords($variable); 67 | 68 | $this->assertEquals(['get', 'All', 'Foo'], $words); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Unit/Infrastructure/Nodes/NodeValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($isAVariable); 26 | } 27 | 28 | /** 29 | * @test 30 | */ 31 | public function it_able_to_check_node_is_a_method(): void 32 | { 33 | $method = new ClassMethod( 34 | name: 'getAllFoo', 35 | ); 36 | 37 | $isAVariable = NodeValidator::isAMethod($method); 38 | 39 | $this->assertTrue($isAVariable); 40 | } 41 | 42 | /** 43 | * @test 44 | */ 45 | public function it_able_to_handle_an_unexpected_node(): void 46 | { 47 | $variable = new Array_( 48 | items: [], 49 | attributes: [], 50 | ); 51 | 52 | $isAVariable = NodeValidator::isAVariable($variable); 53 | 54 | $this->assertFalse($isAVariable); 55 | } 56 | } 57 | --------------------------------------------------------------------------------