Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[config] Extract RectorConfigValidator and handle config validation there #5404

Merged
merged 2 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
133 changes: 28 additions & 105 deletions packages/Config/RectorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Rector\Core\ValueObject\PhpVersion;
use Rector\Core\ValueObject\PolyfillPackage;
use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver;
use Rector\Validation\RectorConfigValidator;
use Webmozart\Assert\Assert;

/**
Expand All @@ -41,6 +42,7 @@ public function paths(array $paths): void
{
Assert::allString($paths);

// ensure paths exist
foreach ($paths as $path) {
if (str_contains($path, '*')) {
continue;
Expand Down Expand Up @@ -81,10 +83,14 @@ public function enableCollectors(): void
SimpleParameterProvider::setParameter(Option::COLLECTORS, true);
}

public function parallel(int $seconds = 120, int $maxNumberOfProcess = 16, int $jobSize = 15): void
/**
* Defaults in sync with https://phpstan.org/config-reference#parallel-processing
* as we run PHPStan as well
*/
public function parallel(int $processTimeout = 60, int $maxNumberOfProcess = 32, int $jobSize = 20): void
{
SimpleParameterProvider::setParameter(Option::PARALLEL, true);
SimpleParameterProvider::setParameter(Option::PARALLEL_JOB_TIMEOUT_IN_SECONDS, $seconds);
SimpleParameterProvider::setParameter(Option::PARALLEL_JOB_TIMEOUT_IN_SECONDS, $processTimeout);
SimpleParameterProvider::setParameter(Option::PARALLEL_MAX_NUMBER_OF_PROCESSES, $maxNumberOfProcess);
SimpleParameterProvider::setParameter(Option::PARALLEL_JOB_SIZE, $jobSize);
}
Expand All @@ -100,51 +106,14 @@ public function memoryLimit(string $memoryLimit): void
}

/**
* @param array<int|string, mixed> $criteria
* @see https://getrector.com/documentation/ignoring-rules-or-paths
* @param array<int|string, mixed> $skip
*/
public function skip(array $criteria): void
{
$notExistsRules = [];
foreach ($criteria as $key => $value) {
/**
* Cover define rule then list of files
*
* $rectorConfig->skip([
* RenameVariableToMatchMethodCallReturnTypeRector::class => [
* __DIR__ . '/packages/Config/RectorConfig.php'
* ],
* ]);
*/
if ($this->isRuleNoLongerExists($key)) {
$notExistsRules[] = $key;
}

if (! is_string($value)) {
continue;
}

/**
* Cover direct value without array list of files, eg:
*
* $rectorConfig->skip([
* StringClassNameToClassConstantRector::class,
* ]);
*/
if ($this->isRuleNoLongerExists($value)) {
$notExistsRules[] = $value;
}
}

if ($notExistsRules !== []) {
throw new ShouldNotHappenException(
'Following rules on $rectorConfig->skip() do no longer exist or changed to different namespace: ' . implode(
', ',
$notExistsRules
)
);
}
public function skip(array $skip): void
{
RectorConfigValidator::ensureRectorRulesExist($skip);

SimpleParameterProvider::addParameter(Option::SKIP, $criteria);
SimpleParameterProvider::addParameter(Option::SKIP, $skip);
}

public function removeUnusedImports(bool $removeUnusedImports = true): void
Expand Down Expand Up @@ -233,6 +202,16 @@ public function rule(string $rectorClass): void
SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass);
}

/**
* @param array<class-string<Collector>> $collectorClasses
*/
public function collectors(array $collectorClasses): void
{
foreach ($collectorClasses as $collectorClass) {
$this->collector($collectorClass);
}
}

/**
* @param class-string<Collector> $collectorClass
*/
Expand Down Expand Up @@ -266,7 +245,8 @@ public function import(string $filePath): void
public function rules(array $rectorClasses): void
{
Assert::allString($rectorClasses);
$this->ensureNotDuplicatedClasses($rectorClasses);

RectorConfigValidator::ensureNoDuplicatedClasses($rectorClasses);

foreach ($rectorClasses as $rectorClass) {
$this->rule($rectorClass);
Expand Down Expand Up @@ -365,16 +345,9 @@ public function indent(string $character, int $count): void
}

/**
* @api deprecated, just for BC layer warning
* @internal
* @api used only in tests
*/
public function services(): void
{
trigger_error(
'The services() method is deprecated. Use $rectorConfig->singleton(ServiceType::class) instead',
E_USER_ERROR
);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is no longer needed 👍


public function resetRuleConfigurations(): void
{
$this->ruleConfigurations = [];
Expand Down Expand Up @@ -429,54 +402,4 @@ public function singleton($abstract, mixed $concrete = null): void
$this->tag($abstract, $autotagInterface);
}
}

private function isRuleNoLongerExists(mixed $skipRule): bool
{
return // only validate string
is_string($skipRule)
// not regex path
&& ! str_contains($skipRule, '*')
// a Rector end
&& str_ends_with($skipRule, 'Rector')
// not directory
&& ! is_dir($skipRule)
// not file
&& ! is_file($skipRule)
// class not exists
&& ! class_exists($skipRule);
}

/**
* @param string[] $values
* @return string[]
*/
private function resolveDuplicatedValues(array $values): array
{
$counted = array_count_values($values);
$duplicates = [];

foreach ($counted as $value => $count) {
if ($count > 1) {
$duplicates[] = $value;
}
}

return array_unique($duplicates);
}

/**
* @param string[] $rectorClasses
*/
private function ensureNotDuplicatedClasses(array $rectorClasses): void
{
$duplicatedRectorClasses = $this->resolveDuplicatedValues($rectorClasses);
if ($duplicatedRectorClasses === []) {
return;
}

throw new ShouldNotHappenException('Following rules are registered twice: ' . implode(
', ',
$duplicatedRectorClasses
));
}
}
108 changes: 108 additions & 0 deletions packages/Validation/RectorConfigValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace Rector\Validation;

use Rector\Core\Exception\ShouldNotHappenException;

final class RectorConfigValidator
{
/**
* @param string[] $rectorClasses
*/
public static function ensureNoDuplicatedClasses(array $rectorClasses): void
{
$duplicatedRectorClasses = self::resolveDuplicatedValues($rectorClasses);
if ($duplicatedRectorClasses === []) {
return;
}

throw new ShouldNotHappenException('Following rules are registered twice: ' . implode(
', ',
$duplicatedRectorClasses
));
}

/**
* @param mixed[] $skip
*/
public static function ensureRectorRulesExist(array $skip): void
{
$nonExistingRules = [];

foreach ($skip as $key => $value) {
if (self::isRectorClassValue($key) && ! class_exists($key)) {
$nonExistingRules[] = $key;
continue;
}

if (! self::isRectorClassValue($value)) {
continue;
}

if (class_exists($value)) {
continue;
}

$nonExistingRules[] = $value;
}

if ($nonExistingRules === []) {
return;
}

$nonExistingRulesString = '';
foreach ($nonExistingRules as $nonExistingRule) {
$nonExistingRulesString .= ' * ' . $nonExistingRule . PHP_EOL;
}

throw new ShouldNotHappenException(
'These rules from "$rectorConfig->skip()" does not exist - remove them or fix their names:' . PHP_EOL . $nonExistingRulesString
);
}

private static function isRectorClassValue(mixed $value): bool
{
// only validate string
if (! is_string($value)) {
return false;
}

// not regex path
if (str_contains($value, '*')) {
return false;
}

// not if no Rector suffix
if (! str_ends_with($value, 'Rector')) {
return false;
}

// not directory
if (is_dir($value)) {
return false;
}

// not file
return ! is_file($value);
}

/**
* @param string[] $values
* @return string[]
*/
private static function resolveDuplicatedValues(array $values): array
{
$counted = array_count_values($values);
$duplicates = [];

foreach ($counted as $value => $count) {
if ($count > 1) {
$duplicates[] = $value;
}
}

return array_unique($duplicates);
}
}
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ parameters:
- src/Bootstrap/ExtensionConfigResolver.php
- src/DependencyInjection/LazyContainerFactory.php
- packages/Config/RectorConfig.php
- packages/Validation/RectorConfigValidator.php

# use of internal phpstan classes
-
Expand Down