RuleSetTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /*
  3. * This file is part of PHP CS Fixer.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace PhpCsFixer\Tests;
  12. use PhpCsFixer\FixerFactory;
  13. use PhpCsFixer\RuleSet;
  14. use PhpCsFixer\Test\AccessibleObject;
  15. use PHPUnit\Framework\TestCase;
  16. /**
  17. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  18. *
  19. * @internal
  20. *
  21. * @covers \PhpCsFixer\RuleSet
  22. */
  23. final class RuleSetTest extends TestCase
  24. {
  25. public function testCreate()
  26. {
  27. $ruleSet = RuleSet::create();
  28. $this->assertInstanceOf(\PhpCsFixer\RuleSet::class, $ruleSet);
  29. }
  30. /**
  31. * @param string $rule
  32. *
  33. * @dataProvider provideAllRulesFromSets
  34. */
  35. public function testIfAllRulesInSetsExists($rule)
  36. {
  37. $factory = new FixerFactory();
  38. $factory->registerBuiltInFixers();
  39. $fixers = [];
  40. foreach ($factory->getFixers() as $fixer) {
  41. $fixers[$fixer->getName()] = $fixer;
  42. }
  43. $this->assertArrayHasKey($rule, $fixers);
  44. }
  45. public function provideAllRulesFromSets()
  46. {
  47. $cases = [];
  48. foreach (RuleSet::create()->getSetDefinitionNames() as $setName) {
  49. $cases = array_merge($cases, RuleSet::create([$setName => true])->getRules());
  50. }
  51. return array_map(
  52. function ($item) {
  53. return [$item];
  54. },
  55. array_keys($cases)
  56. );
  57. }
  58. public function testResolveRulesWithInvalidSet()
  59. {
  60. $this->setExpectedException(
  61. \InvalidArgumentException::class,
  62. 'Set "@foo" does not exist.'
  63. );
  64. RuleSet::create([
  65. '@foo' => true,
  66. ]);
  67. }
  68. public function testResolveRulesWithMissingRuleValue()
  69. {
  70. $this->setExpectedException(
  71. \InvalidArgumentException::class,
  72. 'Missing value for "braces" rule/set.'
  73. );
  74. RuleSet::create([
  75. 'braces',
  76. ]);
  77. }
  78. public function testResolveRulesWithSet()
  79. {
  80. $ruleSet = RuleSet::create([
  81. '@PSR1' => true,
  82. 'braces' => true,
  83. 'encoding' => false,
  84. 'line_ending' => true,
  85. 'strict_comparison' => true,
  86. ]);
  87. $this->assertSameRules(
  88. [
  89. 'braces' => true,
  90. 'full_opening_tag' => true,
  91. 'line_ending' => true,
  92. 'strict_comparison' => true,
  93. ],
  94. $ruleSet->getRules()
  95. );
  96. }
  97. public function testResolveRulesWithNestedSet()
  98. {
  99. $ruleSet = RuleSet::create([
  100. '@PSR2' => true,
  101. 'strict_comparison' => true,
  102. ]);
  103. $this->assertSameRules(
  104. [
  105. 'blank_line_after_namespace' => true,
  106. 'braces' => true,
  107. 'class_definition' => true,
  108. 'elseif' => true,
  109. 'encoding' => true,
  110. 'full_opening_tag' => true,
  111. 'function_declaration' => true,
  112. 'indentation_type' => true,
  113. 'line_ending' => true,
  114. 'lowercase_constants' => true,
  115. 'lowercase_keywords' => true,
  116. 'method_argument_space' => ['ensure_fully_multiline' => true],
  117. 'no_closing_tag' => true,
  118. 'no_spaces_after_function_name' => true,
  119. 'no_spaces_inside_parenthesis' => true,
  120. 'no_trailing_whitespace' => true,
  121. 'no_trailing_whitespace_in_comment' => true,
  122. 'single_blank_line_at_eof' => true,
  123. 'single_class_element_per_statement' => ['elements' => ['property']],
  124. 'single_import_per_statement' => true,
  125. 'single_line_after_imports' => true,
  126. 'strict_comparison' => true,
  127. 'switch_case_semicolon_to_colon' => true,
  128. 'switch_case_space' => true,
  129. 'visibility_required' => true,
  130. ],
  131. $ruleSet->getRules()
  132. );
  133. }
  134. public function testResolveRulesWithDisabledSet()
  135. {
  136. $ruleSet = RuleSet::create([
  137. '@PSR2' => true,
  138. '@PSR1' => false,
  139. 'encoding' => true,
  140. ]);
  141. $this->assertSameRules(
  142. [
  143. 'blank_line_after_namespace' => true,
  144. 'braces' => true,
  145. 'class_definition' => true,
  146. 'elseif' => true,
  147. 'encoding' => true,
  148. 'function_declaration' => true,
  149. 'indentation_type' => true,
  150. 'line_ending' => true,
  151. 'lowercase_constants' => true,
  152. 'lowercase_keywords' => true,
  153. 'method_argument_space' => ['ensure_fully_multiline' => true],
  154. 'no_closing_tag' => true,
  155. 'no_spaces_after_function_name' => true,
  156. 'no_spaces_inside_parenthesis' => true,
  157. 'no_trailing_whitespace' => true,
  158. 'no_trailing_whitespace_in_comment' => true,
  159. 'single_blank_line_at_eof' => true,
  160. 'single_class_element_per_statement' => ['elements' => ['property']],
  161. 'single_import_per_statement' => true,
  162. 'single_line_after_imports' => true,
  163. 'switch_case_semicolon_to_colon' => true,
  164. 'switch_case_space' => true,
  165. 'visibility_required' => true,
  166. ],
  167. $ruleSet->getRules()
  168. );
  169. }
  170. /**
  171. * @dataProvider providerSetDefinitionNames
  172. *
  173. * @param string $setDefinitionName
  174. */
  175. public function testSetDefinitionsAreSorted($setDefinitionName)
  176. {
  177. $ruleSet = RuleSet::create();
  178. $method = new \ReflectionMethod(
  179. \PhpCsFixer\RuleSet::class,
  180. 'getSetDefinition'
  181. );
  182. $method->setAccessible(true);
  183. $setDefinition = $method->invoke(
  184. $ruleSet,
  185. $setDefinitionName
  186. );
  187. $sortedSetDefinition = $setDefinition;
  188. $this->sort($sortedSetDefinition);
  189. $this->assertSame($sortedSetDefinition, $setDefinition, sprintf(
  190. 'Failed to assert that the set definition for "%s" is sorted by key',
  191. $setDefinitionName
  192. ));
  193. }
  194. /**
  195. * @return array
  196. */
  197. public function providerSetDefinitionNames()
  198. {
  199. $setDefinitionNames = RuleSet::create()->getSetDefinitionNames();
  200. return array_map(function ($setDefinitionName) {
  201. return [$setDefinitionName];
  202. }, $setDefinitionNames);
  203. }
  204. /**
  205. * @param array $set
  206. * @param bool $safe
  207. *
  208. * @dataProvider provideSafeSets
  209. */
  210. public function testRiskyRulesInSet(array $set, $safe)
  211. {
  212. $fixers = FixerFactory::create()
  213. ->registerBuiltInFixers()
  214. ->useRuleSet(new RuleSet($set))
  215. ->getFixers()
  216. ;
  217. $fixerNames = [];
  218. foreach ($fixers as $fixer) {
  219. if ($safe === $fixer->isRisky()) {
  220. $fixerNames[] = $fixer->getName();
  221. }
  222. }
  223. $this->assertCount(
  224. 0,
  225. $fixerNames,
  226. sprintf(
  227. 'Set should only contain %s fixers, got: \'%s\'.',
  228. $safe ? 'safe' : 'risky',
  229. implode('\', \'', $fixerNames)
  230. )
  231. );
  232. }
  233. public function provideSafeSets()
  234. {
  235. return [
  236. [['@PSR1' => true], true],
  237. [['@PSR2' => true], true],
  238. [['@Symfony' => true], true],
  239. [
  240. [
  241. '@Symfony:risky' => true,
  242. '@Symfony' => false,
  243. ],
  244. false,
  245. ],
  246. [
  247. [
  248. '@Symfony:risky' => true,
  249. ],
  250. false,
  251. ],
  252. ];
  253. }
  254. public function testInvalidConfigNestedSets()
  255. {
  256. $this->setExpectedExceptionRegExp(
  257. \UnexpectedValueException::class,
  258. '#^Nested rule set "@PSR1" configuration must be a boolean\.$#'
  259. );
  260. new RuleSet(
  261. ['@PSR1' => ['@PSR2' => 'no']]
  262. );
  263. }
  264. public function testGetSetDefinitionNames()
  265. {
  266. $ruleSet = $this->createRuleSetToTestWith([]);
  267. $this->assertSame(
  268. array_keys(self::getRuleSetDefinitionsToTestWith()),
  269. $ruleSet->getSetDefinitionNames()
  270. );
  271. }
  272. /**
  273. * @param array $expected
  274. * @param array $rules
  275. *
  276. * @dataProvider provideResolveRulesCases
  277. */
  278. public function testResolveRules(array $expected, array $rules)
  279. {
  280. $ruleSet = $this->createRuleSetToTestWith($rules);
  281. $this->assertSameRules($expected, $ruleSet->getRules());
  282. }
  283. public function provideResolveRulesCases()
  284. {
  285. return [
  286. '@Foo + C\' -D' => [
  287. ['A' => true, 'B' => true, 'C' => 56],
  288. ['@Foo' => true, 'C' => 56, 'D' => false],
  289. ],
  290. '@Foo + @Bar' => [
  291. ['A' => true, 'B' => true, 'D' => 34, 'E' => true],
  292. ['@Foo' => true, '@Bar' => true],
  293. ],
  294. '@Foo - @Bar' => [
  295. ['B' => true],
  296. ['@Foo' => true, '@Bar' => false],
  297. ],
  298. '@A - @E (set in set)' => [
  299. ['AA' => true], // 'AB' => false, 'AC' => false
  300. ['@A' => true, '@E' => false],
  301. ],
  302. '@A + @E (set in set)' => [
  303. ['AA' => true, 'AB' => '_AB', 'AC' => 'b', 'Z' => true],
  304. ['@A' => true, '@E' => true],
  305. ],
  306. '@E + @A (set in set) + rule override' => [
  307. ['AC' => 'd', 'AB' => true, 'Z' => true, 'AA' => true],
  308. ['@E' => true, '@A' => true, 'AC' => 'd'],
  309. ],
  310. 'nest single set' => [
  311. ['AC' => 'b', 'AB' => '_AB', 'Z' => 'E'],
  312. ['@F' => true],
  313. ],
  314. 'Set reconfigure rule in other set, reconfigure rule.' => [
  315. [
  316. 'AA' => true,
  317. 'AB' => true,
  318. 'AC' => 'abc',
  319. ],
  320. [
  321. '@A' => true,
  322. '@D' => true,
  323. 'AC' => 'abc',
  324. ],
  325. ],
  326. 'Set reconfigure rule in other set.' => [
  327. [
  328. 'AA' => true,
  329. 'AB' => true,
  330. 'AC' => 'b',
  331. ],
  332. [
  333. '@A' => true,
  334. '@D' => true,
  335. ],
  336. ],
  337. 'Set minus two sets minus rule' => [
  338. [
  339. 'AB' => true,
  340. ],
  341. [
  342. '@A' => true,
  343. '@B' => false,
  344. '@C' => false,
  345. 'AC' => false,
  346. ],
  347. ],
  348. 'Set minus two sets' => [
  349. [
  350. 'AB' => true,
  351. 'AC' => 'a',
  352. ],
  353. [
  354. '@A' => true,
  355. '@B' => false,
  356. '@C' => false,
  357. ],
  358. ],
  359. 'Set minus rule test.' => [
  360. [
  361. 'AA' => true,
  362. 'AC' => 'a',
  363. ],
  364. [
  365. '@A' => true,
  366. 'AB' => false,
  367. ],
  368. ],
  369. 'Set minus set test.' => [
  370. [
  371. 'AB' => true,
  372. 'AC' => 'a',
  373. ],
  374. [
  375. '@A' => true,
  376. '@B' => false,
  377. ],
  378. ],
  379. 'Set to rules test.' => [
  380. [
  381. 'AA' => true,
  382. 'AB' => true,
  383. 'AC' => 'a',
  384. ],
  385. [
  386. '@A' => true,
  387. ],
  388. ],
  389. '@A - @C' => [
  390. [
  391. 'AB' => true,
  392. 'AC' => 'a',
  393. ],
  394. [
  395. '@A' => true,
  396. '@C' => false,
  397. ],
  398. ],
  399. '@A - @D' => [
  400. [
  401. 'AA' => true,
  402. 'AB' => true,
  403. ],
  404. [
  405. '@A' => true,
  406. '@D' => false,
  407. ],
  408. ],
  409. ];
  410. }
  411. public function testGetMissingRuleConfiguration()
  412. {
  413. $ruleSet = new RuleSet();
  414. $this->setExpectedExceptionRegExp(
  415. \InvalidArgumentException::class,
  416. '#^Rule "_not_exists" is not in the set\.$#'
  417. );
  418. $ruleSet->getRuleConfiguration('_not_exists');
  419. }
  420. private function assertSameRules(array $expected, array $actual, $message = '')
  421. {
  422. ksort($expected);
  423. ksort($actual);
  424. $this->assertSame($expected, $actual, $message);
  425. }
  426. /**
  427. * Sorts an array of rule set definitions recursively.
  428. *
  429. * Sometimes keys are all string, sometimes they are integers - we need to account for that.
  430. *
  431. * @param array $data
  432. */
  433. private function sort(array &$data)
  434. {
  435. $keys = array_keys($data);
  436. if ($this->allInteger($keys)) {
  437. sort($data);
  438. } else {
  439. ksort($data);
  440. }
  441. foreach ($data as $key => $value) {
  442. if (is_array($value)) {
  443. $this->sort($data[$key]);
  444. }
  445. }
  446. }
  447. /**
  448. * @param array $values
  449. *
  450. * @return bool
  451. */
  452. private function allInteger(array $values)
  453. {
  454. foreach ($values as $value) {
  455. if (!is_int($value)) {
  456. return false;
  457. }
  458. }
  459. return true;
  460. }
  461. private function createRuleSetToTestWith(array $rules)
  462. {
  463. $ruleSet = new RuleSet();
  464. $reflection = new AccessibleObject($ruleSet);
  465. $reflection->setDefinitions = self::getRuleSetDefinitionsToTestWith();
  466. $reflection->set = $rules;
  467. $reflection->resolveSet();
  468. return $ruleSet;
  469. }
  470. private static function getRuleSetDefinitionsToTestWith()
  471. {
  472. static $testSet = [
  473. '@A' => [
  474. 'AA' => true,
  475. 'AB' => true,
  476. 'AC' => 'a',
  477. ],
  478. '@B' => [
  479. 'AA' => true,
  480. ],
  481. '@C' => [
  482. 'AA' => false,
  483. ],
  484. '@D' => [
  485. 'AC' => 'b',
  486. ],
  487. '@E' => [
  488. '@D' => true,
  489. 'AB' => '_AB',
  490. 'Z' => true,
  491. ],
  492. '@F' => [
  493. '@E' => true,
  494. 'Z' => 'E',
  495. ],
  496. '@Foo' => ['A' => true, 'B' => true, 'C' => true, 'D' => 12],
  497. '@Bar' => ['A' => true, 'C' => false, 'D' => 34, 'E' => true, 'F' => false],
  498. ];
  499. return $testSet;
  500. }
  501. }