private function checkMethodBody(Tokens $tokens, $methodName, $methodStart, $methodEnd) { for ($index = $methodStart; $index < $methodEnd; ++$index) { if (!$tokens[$index]->isGivenKind(T_STRING) || !in_array($tokens[$index]->getContent(), ['func_get_args', 'func_get_arg', 'func_num_args'], true)) { continue; } $tokens->reportAt($index, new Message(E_ERROR, 'check_method_arguments_called_not_allowed', ['method' => $methodName, 'function' => $tokens[$index]->getContent()])); break; } }
/** * @param array $expectedMessages The expected list of messages * @param string $input The input that is tokenized * @param \SplFileInfo|null $file * @param CheckerInterface|null $checker */ protected function makeTest(array $expectedMessages, $input, \SplFileInfo $file = null, CheckerInterface $checker = null) { $checker = $checker ?: $this->getChecker(); $file = $file ?: $this->getTestFile(); $fileIsSupported = $checker->supports($file); Tokens::clearCache(); $tokens = Tokens::fromCode($input); if ($fileIsSupported) { self::assertTrue($checker->isCandidate($tokens), 'Fixer must be a candidate for input code.'); $checker->check($file, $tokens); } $iterator = new ReportedTokenIterator(new LineNumberIterator($tokens)); $lineMessages = []; foreach ($iterator as $k => $messages) { $lineMessages[$k] = array_map(function (Message $message) { return $message->toArray(); }, $messages); } self::assertEquals($expectedMessages, $lineMessages); }
/** * @inheritdoc */ public function check(\SplFileInfo $file, Tokens $tokens) { $tokenCount = $tokens->count(); for ($index = 0; $index < $tokenCount; ++$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_CLASS)) { continue; } $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; $classStart = $tokens->getNextTokenOfKind($index, array('{')); $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); // ignore class if it is abstract or already final if ($prevToken->isGivenKind(array(T_ABSTRACT, T_FINAL))) { $index = $classEnd; continue; } $classNameIndex = $tokens->getNextTokenOfKind($index, [[T_STRING]]); $className = $tokens[$classNameIndex]->getContent(); $tokens->reportAt($index, new Message(E_ERROR, 'check_class_should_be_final', ['class' => $className])); $index = $classEnd; } }
/** * @param \SplFileInfo $file * @param \Symfony\CS\FixerInterface[] $fixers * @param CheckerInterface[] $checkers * @param bool $dryRun * @param bool $diff * @param FileCacheManager $fileCacheManager * @param FileMessageCacheManager $messageCacheManager * * @return array|null|void */ public function fixFile(\SplFileInfo $file, array $fixers, $dryRun, $diff, FileCacheManager $fileCacheManager, array $checkers = array(), FileMessageCacheManager $messageCacheManager = null) { $new = $old = file_get_contents($file->getRealpath()); if ('' === $old || !$fileCacheManager->needFixing($this->getFileRelativePathname($file), $old) || PHP_VERSION_ID >= 50306 && PHP_VERSION_ID < 50400 && false !== stripos($old, '__halt_compiler()')) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_SKIPPED)); return false; } try { $this->linter->lintFile($file->getRealpath()); } catch (LintingException $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_INVALID)); $this->errorsManager->report(new Error(Error::TYPE_INVALID, $this->getFileRelativePathname($file))); return false; } $old = file_get_contents($file->getRealpath()); // we do not need Tokens to still caching previously fixed file - so clear the cache Tokens::clearCache(); $tokens = Tokens::fromCode($old); $newHash = $oldHash = $tokens->getCodeHash(); $checkMessages = []; try { if ($dryRun) { $checkMessages = $this->runCheckers($file, $checkers, $tokens); } $appliedFixers = $this->runFixers($file, $fixers, $tokens); if (!$dryRun) { $checkMessages = $this->runCheckers($file, $checkers, $tokens); } } catch (\Exception $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_EXCEPTION)); $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $this->getFileRelativePathname($file))); return false; } $fixInfo = null; if (!empty($appliedFixers)) { $new = $tokens->generateCode(); $newHash = $tokens->getCodeHash(); } // We need to check if content was changed and then applied changes. // But we can't simple check $appliedFixers, because one fixer may revert // work of other and both of them will mark collection as changed. // Therefore we need to check if code hashes changed. if ($oldHash !== $newHash) { try { $this->linter->lintSource($new); } catch (LintingException $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_LINT)); $this->errorsManager->report(new Error(Error::TYPE_LINT, $this->getFileRelativePathname($file))); return false; } if (!$dryRun && false === @file_put_contents($file->getRealpath(), $new)) { $error = error_get_last(); if ($error) { throw new IOException(sprintf('Failed to write file "%s", "%s".', $file->getRealpath(), $error['message']), 0, null, $file->getRealpath()); } throw new IOException(sprintf('Failed to write file "%s".', $file->getRealpath()), 0, null, $file->getRealpath()); } $fixInfo = array('appliedFixers' => $appliedFixers, 'checkMessages' => $checkMessages); if ($diff) { $fixInfo['diff'] = $this->stringDiff($old, $new); } } elseif (!empty($checkMessages)) { $fixInfo = array('checkMessages' => $checkMessages); } $fileCacheManager->setFile($this->getFileRelativePathname($file), $new); if ($messageCacheManager !== null) { $messageCacheManager->setMessages($this->getFileRelativePathname($file), isset($fixInfo['checkMessages']) ? $fixInfo['checkMessages'] : []); } $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES)); return $fixInfo; }
private function checkMethod(Tokens $tokens, $index, $attributes) { if ($attributes['name'] !== '__construct') { $tokens->reportAt($index, new Message(E_ERROR, 'check_class_must_not_contain_method', ['method' => $attributes['name']])); return false; } if ($attributes['visibility'] === null) { $tokens->reportAt($index, new Message(E_ERROR, 'check_class_method_must_have_a_visibility', ['method' => $attributes['name']])); } elseif ($attributes['visibility'] !== T_PUBLIC) { $tokens->reportAt($index, new Message(E_ERROR, 'check_class_method_must_be_public', ['method' => $attributes['name'], 'visibility' => $attributes['visibility'] === T_PRIVATE ? 'private' : 'protected'])); } $functionArguments = TokensHelper::getFunctionArguments($tokens, $index); // Ensure that all arguments are used $startBodyIndex = $tokens->getNextTokenOfKind($index, ['{']); $endBodyIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBodyIndex, true); $usedVariables = []; for ($index = $endBodyIndex - 1; $index > $startBodyIndex; --$index) { $token = $tokens[$index]; if (!$token->isGivenKind(T_VARIABLE) || $tokens[$index - 1]->isGivenKind(T_OBJECT_OPERATOR) || $tokens[$index + 1]->isGivenKind(T_OBJECT_OPERATOR)) { continue; } $usedVariables[$index] = $token->getContent(); } // Report unused arguments $unusedArguments = array_diff(array_map(function ($info) { return $info['token']->getContent(); }, $functionArguments), $usedVariables); foreach ($unusedArguments as $argumentIndex => $argument) { $tokens->reportAt($argumentIndex, new Message(E_ERROR, 'check_class_method_argument_not_used', ['method' => $attributes['name'], 'argument' => $argument])); } return ['arguments' => $functionArguments]; }
/** * @param \SplFileInfo $file * @param Tokens $tokens * @param $namespace * @param $namespaceIndex */ public function checkNamespaceDirectory(\SplFileInfo $file, Tokens $tokens, $namespace, $namespaceIndex) { if ($namespace === null) { return; } $path = trim(preg_replace('#/+#', '/', $file->getPath()), '/'); $parts = []; while (!isset($this->dirMap[$path])) { $pathPos = strrpos($path, '/'); if ($pathPos === false) { return; } $parts[] = substr($path, $pathPos + 1); $path = substr($path, 0, $pathPos); } if (!isset($this->dirMap[$path])) { return; } $expectedNamespace = $this->dirMap[$path] . (empty($parts) ? '' : '\\' . implode('\\', array_reverse($parts))); if ($expectedNamespace !== $namespace) { $tokens->reportAt($namespaceIndex, new Message(E_ERROR, 'check_psr4_namespace_is_invalid', ['expected' => $expectedNamespace, 'namespace' => $namespace])); } }