Skip to content

Commit

Permalink
[config] Extract RectorConfigValidator and handle config validation t…
Browse files Browse the repository at this point in the history
…here (#5404)

* [config] Extract RectorConfigValidator and handle config validation there

* note, down timeout to 60 seconds to spot bottle necks sooner
  • Loading branch information
TomasVotruba committed Dec 31, 2023
1 parent 9c7842f commit 0ba4706
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 105 deletions.
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
);
}

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

0 comments on commit 0ba4706

Please sign in to comment.