RuleSet.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\RuleSet;
  13. use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
  14. use PhpCsFixer\Utils;
  15. /**
  16. * Set of rules to be used by fixer.
  17. *
  18. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  19. *
  20. * @internal
  21. */
  22. final class RuleSet implements RuleSetInterface
  23. {
  24. /**
  25. * Group of rules generated from input set.
  26. *
  27. * The key is name of rule, value is configuration array or true.
  28. * The key must not point to any set.
  29. *
  30. * @var array<string, array<string, mixed>|true>
  31. */
  32. private array $rules;
  33. public function __construct(array $set = [])
  34. {
  35. foreach ($set as $name => $value) {
  36. if ('' === $name) {
  37. throw new \InvalidArgumentException('Rule/set name must not be empty.');
  38. }
  39. if (\is_int($name)) {
  40. throw new \InvalidArgumentException(sprintf('Missing value for "%s" rule/set.', $value));
  41. }
  42. if (!\is_bool($value) && !\is_array($value)) {
  43. $message = str_starts_with($name, '@') ? 'Set must be enabled (true) or disabled (false). Other values are not allowed.' : 'Rule must be enabled (true), disabled (false) or configured (non-empty, assoc array). Other values are not allowed.';
  44. if (null === $value) {
  45. $message .= ' To disable the '.(str_starts_with($name, '@') ? 'set' : 'rule').', use "FALSE" instead of "NULL".';
  46. }
  47. throw new InvalidFixerConfigurationException($name, $message);
  48. }
  49. }
  50. $this->resolveSet($set);
  51. }
  52. public function hasRule(string $rule): bool
  53. {
  54. return \array_key_exists($rule, $this->rules);
  55. }
  56. public function getRuleConfiguration(string $rule): ?array
  57. {
  58. if (!$this->hasRule($rule)) {
  59. throw new \InvalidArgumentException(sprintf('Rule "%s" is not in the set.', $rule));
  60. }
  61. if (true === $this->rules[$rule]) {
  62. return null;
  63. }
  64. return $this->rules[$rule];
  65. }
  66. public function getRules(): array
  67. {
  68. return $this->rules;
  69. }
  70. /**
  71. * Resolve input set into group of rules.
  72. *
  73. * @param array<string, array<string, mixed>|bool> $rules
  74. */
  75. private function resolveSet(array $rules): void
  76. {
  77. $resolvedRules = [];
  78. // expand sets
  79. foreach ($rules as $name => $value) {
  80. if (str_starts_with($name, '@')) {
  81. if (!\is_bool($value)) {
  82. throw new \UnexpectedValueException(sprintf('Nested rule set "%s" configuration must be a boolean.', $name));
  83. }
  84. $set = $this->resolveSubset($name, $value);
  85. $resolvedRules = array_merge($resolvedRules, $set);
  86. } else {
  87. $resolvedRules[$name] = $value;
  88. }
  89. }
  90. // filter out all resolvedRules that are off
  91. $resolvedRules = array_filter(
  92. $resolvedRules,
  93. static fn ($value): bool => false !== $value
  94. );
  95. $this->rules = $resolvedRules;
  96. }
  97. /**
  98. * Resolve set rules as part of another set.
  99. *
  100. * If set value is false then disable all fixers in set,
  101. * if not then get value from set item.
  102. *
  103. * @return array<string, array<string, mixed>|bool>
  104. */
  105. private function resolveSubset(string $setName, bool $setValue): array
  106. {
  107. $ruleSet = RuleSets::getSetDefinition($setName);
  108. if ($ruleSet instanceof DeprecatedRuleSetDescriptionInterface) {
  109. $messageEnd = [] === $ruleSet->getSuccessorsNames()
  110. ? 'No replacement available'
  111. : sprintf('Use %s instead', Utils::naturalLanguageJoin($ruleSet->getSuccessorsNames()));
  112. Utils::triggerDeprecation(new \RuntimeException("Rule set \"{$setName}\" is deprecated. {$messageEnd}."));
  113. }
  114. $rules = $ruleSet->getRules();
  115. foreach ($rules as $name => $value) {
  116. if (str_starts_with($name, '@')) {
  117. $set = $this->resolveSubset($name, $setValue);
  118. unset($rules[$name]);
  119. $rules = array_merge($rules, $set);
  120. } elseif (!$setValue) {
  121. $rules[$name] = false;
  122. } else {
  123. $rules[$name] = $value;
  124. }
  125. }
  126. return $rules;
  127. }
  128. }