public function testGetClassyElements() { $source = <<<'PHP' <?php class Foo { public $prop0; protected $prop1; private $prop2 = 1; var $prop3 = array(1,2,3); public function bar4() { $a = 5; return " ({$a})"; } public function bar5($data) { } } PHP; $tokens = Tokens::fromCode($source); $tokensAnalyzer = new TokensAnalyzer($tokens); $elements = array_values($tokensAnalyzer->getClassyElements()); $this->assertCount(6, $elements); $this->assertSame('property', $elements[0]['type']); $this->assertSame('property', $elements[1]['type']); $this->assertSame('property', $elements[2]['type']); $this->assertSame('property', $elements[3]['type']); $this->assertSame('method', $elements[4]['type']); $this->assertSame('method', $elements[5]['type']); }
/** * @inheritdoc */ public function check(\SplFileInfo $file, Tokens $tokens) { $analyzer = new TokensAnalyzer($tokens); $constructorIndex = null; $constructorArgs = []; $properties = []; // Find the class $classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]); if ($classIndex === null) { return; } $classAttributes = TokensHelper::getClassAttributes($tokens, $classIndex); // Event must be final or abstract if (!$classAttributes['abstract'] && !$classAttributes['final']) { $tokens->reportAt($classIndex, new Message(E_ERROR, 'check_class_must_be_final_or_abstract', ['class' => $classAttributes['name']])); } $elements = $analyzer->getClassyElements(); foreach ($elements as $index => $element) { /** @var Token $token */ if ($element['type'] === 'method') { $methodAttributes = TokensHelper::getMethodAttributes($tokens, $index, $analyzer); if ($methodInfo = $this->checkMethod($tokens, $index, $methodAttributes)) { $constructorIndex = $index; $constructorArgs = $methodInfo['arguments']; } } elseif ($element['type'] === 'property' && ($propertyInfo = $this->checkProperty($tokens, $index))) { $properties[$index] = $propertyInfo; } } // A event without data is not a error if ($constructorIndex === null && count($properties) === 0) { return; } // Event with properties must have a constructor if ($constructorIndex === null) { $tokens->reportAt($classIndex, new Message(E_ERROR, 'check_class_must_have_constructor', ['class' => $classAttributes['name']])); } else { $expectedProperties = array_map(function ($info) { return $info['token']->getContent(); }, $constructorArgs); $unknownConstructorArgs = array_diff($expectedProperties, $properties); if (count($unknownConstructorArgs) > 0) { $tokens->reportAt($constructorIndex, new Message(E_ERROR, 'check_class_constructor_has_arguments_without_related_property', ['arguments' => implode(', ', $unknownConstructorArgs)])); } $unassignedProperties = array_diff($properties, $expectedProperties); if (count($unassignedProperties) > 0) { foreach ($unassignedProperties as $index => $property) { $tokens->reportAt($index, new Message(E_ERROR, 'check_class_property_must_be_know_as_constructor_argument', ['property' => $property])); } } } }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $elements = $tokensAnalyzer->getClassyElements(); foreach (array_reverse($elements, true) as $index => $element) { if ('method' === $element['type']) { $this->overrideAttribs($tokens, $index, $this->grabAttribsBeforeMethodToken($tokens, $index)); // force whitespace between function keyword and function name to be single space char $afterToken = $tokens[++$index]; if ($afterToken->isWhitespace()) { $afterToken->setContent(' '); } } elseif ('property' === $element['type']) { $prevIndex = $tokens->getPrevTokenOfKind($index, array(';', ',', '{')); if (!$prevIndex || !$tokens[$prevIndex]->equals(',')) { $this->overrideAttribs($tokens, $index, $this->grabAttribsBeforePropertyToken($tokens, $index)); } } } }