/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $uses = array_reverse($tokensAnalyzer->getImportUseIndexes()); foreach ($uses as $index) { $endIndex = $tokens->getNextTokenOfKind($index, array(';')); $declarationContent = $tokens->generatePartialCode($index + 1, $endIndex - 1); $declarationParts = explode(',', $declarationContent); if (1 === count($declarationParts)) { continue; } $declarationContent = array(); foreach ($declarationParts as $declarationPart) { $declarationContent[] = 'use ' . trim($declarationPart) . ';'; } $declarationContent = implode("\n" . $this->detectIndent($tokens, $index), $declarationContent); for ($i = $index; $i <= $endIndex; ++$i) { $tokens[$i]->clear(); } $declarationTokens = Tokens::fromCode('<?php ' . $declarationContent); $declarationTokens[0]->clear(); $declarationTokens->clearEmptyTokens(); $tokens->insertAt($index, $declarationTokens); } }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); $usesOrder = array(); if (!count($namespacesImports)) { return; } foreach ($namespacesImports as $uses) { $uses = array_reverse($uses); $usesOrder = array_replace($usesOrder, $this->getNewOrder($uses, $tokens)); } $usesOrder = array_reverse($usesOrder, true); $mapStartToEnd = array(); foreach ($usesOrder as $use) { $mapStartToEnd[$use[1]] = $use[2]; } // Now insert the new tokens, starting from the end foreach ($usesOrder as $index => $use) { $declarationTokens = Tokens::fromCode('<?php use ' . $use[0] . ';'); $declarationTokens->clearRange(0, 2); // clear `<?php use ` $declarationTokens[count($declarationTokens) - 1]->clear(); // clear `;` $declarationTokens->clearEmptyTokens(); $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); } }
/** * @dataProvider provideProcessCases */ public function testProcess($source, array $expectedTokens) { $tokens = Tokens::fromCode($source); foreach ($expectedTokens as $index => $expectedToken) { $token = $tokens[$index]; $this->assertSame($expectedToken[1], $token->getContent()); $this->assertSame($expectedToken[0], $token->getId()); } }
/** * {@inheritdoc} */ public function lintSource($source) { try { // it will throw ParseError on syntax error // if not, it will cache the tokenized version of code, which is great for Runner Tokens::fromCode($source); return new TokenizerLintingResult(); } catch (\ParseError $e) { return new TokenizerLintingResult($e); } }
protected function doTest($source, array $expectedTokens = array(), array $observedKinds = array()) { $tokens = Tokens::fromCode($source); $this->assertSame(count($expectedTokens), array_sum(array_map(function ($item) { return count($item); }, $tokens->findGivenKind(array_unique(array_merge($observedKinds, array_values($expectedTokens))))))); foreach ($expectedTokens as $index => $tokenId) { $this->assertSame(CT::has($tokenId) ? CT::getName($tokenId) : token_name($tokenId), $tokens[$index]->getName(), sprintf('Token kind should be the same at index %d.', $index)); $this->assertSame($tokenId, $tokens[$index]->getId()); } }
/** * @param int $expected * @param string $code * @param int $index * * @dataProvider provideCommentBlockStartDetectionCases */ public function testCommentBlockStartDetection($expected, $code, $index) { Tokens::clearCache(); $tokens = Tokens::fromCode($code); $fixer = $this->getFixer(); $method = new \ReflectionMethod($fixer, 'findCommentBlockStart'); $method->setAccessible(true); if ($expected !== ($result = $method->invoke($fixer, $tokens, $index))) { $this->fail(sprintf('Expected index %d (%s) got index %d (%s).', $expected, $tokens[$expected]->toJson(), $result, $tokens[$result]->toJson())); } }
protected function doTest($source, array $expectedTokens = array()) { $tokens = Tokens::fromCode($source); $this->assertSame(count($expectedTokens), array_sum(array_map(function ($item) { return count($item); }, $tokens->findGivenKind(array_map(function ($name) { return constant($name); }, $expectedTokens))))); foreach ($expectedTokens as $index => $name) { $this->assertSame($name, $tokens[$index]->getName()); $this->assertSame(constant($name), $tokens[$index]->getId()); } }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokensOrg) { $content = $tokensOrg->generateCode(); // replace all <? with <?php to replace all short open tags even without short_open_tag option enabled $newContent = preg_replace('/<\\?(\\s|$)/', '<?php$1', $content, -1, $count); if (!$count) { return; } /* the following code is magic to revert previous replacements which should NOT be replaced, for example incorrectly replacing * > echo '<? '; * with * > echo '<?php '; */ $tokens = Tokens::fromCode($newContent); $tokensOldContent = ''; $tokensOldContentLength = 0; foreach ($tokens as $token) { if ($token->isGivenKind(T_OPEN_TAG)) { $tokenContent = $token->getContent(); if ('<?php' !== substr($content, $tokensOldContentLength, 5)) { $tokenContent = '<? '; } $tokensOldContent .= $tokenContent; $tokensOldContentLength += strlen($tokenContent); continue; } if ($token->isGivenKind(array(T_COMMENT, T_DOC_COMMENT, T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_STRING))) { $tokenContent = ''; $tokenContentLength = 0; $parts = explode('<?php', $token->getContent()); $iLast = count($parts) - 1; foreach ($parts as $i => $part) { $tokenContent .= $part; $tokenContentLength += strlen($part); if ($i !== $iLast) { if ('<?php' === substr($content, $tokensOldContentLength + $tokenContentLength, 5)) { $tokenContent .= '<?php'; $tokenContentLength += 5; } else { $tokenContent .= '<?'; $tokenContentLength += 2; } } } $token->setContent($tokenContent); } $tokensOldContent .= $token->getContent(); $tokensOldContentLength += strlen($token->getContent()); } $tokensOrg->overrideRange(0, $tokensOrg->count() - 1, $tokens); }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $uses = array_reverse($tokensAnalyzer->getImportUseIndexes()); foreach ($uses as $index) { $endIndex = $tokens->getNextTokenOfKind($index, array(';', array(T_CLOSE_TAG))); $previous = $tokens->getPrevMeaningfulToken($endIndex); if ($tokens[$previous]->equals('}')) { $start = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $previous, false); $declarationContent = $tokens->generatePartialCode($start + 1, $previous - 1); $prefix = ''; for ($i = $index + 1; $i < $start; ++$i) { $prefix .= $tokens[$i]->getContent(); } $prefix = ' ' . ltrim($prefix); } else { $declarationContent = $tokens->generatePartialCode($index + 1, $endIndex - 1); $prefix = ' '; } $declarationParts = explode(',', $declarationContent); if (1 === count($declarationParts)) { continue; } $declarationContent = array(); foreach ($declarationParts as $declarationPart) { $declarationContent[] = 'use' . $prefix . trim($declarationPart) . ';'; } $declarationContent = implode("\n" . $this->detectIndent($tokens, $index), $declarationContent); for ($i = $index; $i < $endIndex; ++$i) { $tokens[$i]->clear(); } if ($tokens[$endIndex]->equals(';')) { $tokens[$endIndex]->clear(); } $declarationTokens = Tokens::fromCode('<?php ' . $declarationContent); $declarationTokens[0]->clear(); $declarationTokens->clearEmptyTokens(); $tokens->insertAt($index, $declarationTokens); } }
/** * {@inheritdoc} */ public function supports(\SplFileInfo $file) { if ($file instanceof StdinFileInfo) { return false; } $filenameParts = explode('.', $file->getBasename(), 2); if (!isset($filenameParts[1]) || 'php' !== $filenameParts[1] || 0 === preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $filenameParts[0]) || '__halt_compiler' === $filenameParts[0]) { return false; } try { $tokens = Tokens::fromCode(sprintf('<?php class %s {}', $filenameParts[0])); if ($tokens[3]->isKeyword() || $tokens[3]->isMagicConstant()) { // name can not be a class name - detected by PHP 5.x return false; } } catch (\ParseError $e) { // name can not be a class name - detected by PHP 7.x return false; } // ignore stubs/fixtures, since they are typically containing invalid files for various reasons return !preg_match('{[/\\\\](stub|fixture)s?[/\\\\]}i', $file->getRealPath()); }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $tokensAnalyzer = new TokensAnalyzer($tokens); $namespacesImports = $tokensAnalyzer->getImportUseIndexes(true); if (0 === count($namespacesImports)) { return; } $usesOrder = array(); foreach ($namespacesImports as $uses) { $usesOrder = array_replace($usesOrder, $this->getNewOrder(array_reverse($uses), $tokens)); } $usesOrder = array_reverse($usesOrder, true); $mapStartToEnd = array(); foreach ($usesOrder as $use) { $mapStartToEnd[$use['startIndex']] = $use['endIndex']; } // Now insert the new tokens, starting from the end foreach ($usesOrder as $index => $use) { $declarationTokens = Tokens::fromCode('<?php use ' . $use['namespace'] . ';'); $declarationTokens->clearRange(0, 2); // clear `<?php use ` $declarationTokens[count($declarationTokens) - 1]->clear(); // clear `;` $declarationTokens->clearEmptyTokens(); $tokens->overrideRange($index, $mapStartToEnd[$index], $declarationTokens); if ($use['group']) { // a group import must start with `use` and cannot be part of comma separated import list $prev = $tokens->getPrevMeaningfulToken($index); if ($tokens[$prev]->equals(',')) { $tokens[$prev]->setContent(';'); $tokens->insertAt($prev + 1, new Token(array(T_USE, 'use'))); if (!$tokens[$prev + 2]->isWhitespace()) { $tokens->insertAt($prev + 2, new Token(array(T_WHITESPACE, ' '))); } } } } }
/** * @dataProvider provideBlockDetectionCases */ public function testBlockDetection(array $expected, $source, $index) { Tokens::clearCache(); $tokens = Tokens::fromCode($source); $fixer = $this->getFixer(); $method = new \ReflectionMethod($fixer, 'getPreviousBlock'); $method->setAccessible(true); $result = $method->invoke($fixer, $tokens, $index); $this->assertSame($expected, $result); }
/** * @dataProvider provideFindHeaderCommentInsertionIndexCases */ public function testFindHeaderCommentInsertionIndex($expected, $code, array $config) { Tokens::clearCache(); $tokens = Tokens::fromCode($code); $fixer = $this->getFixer(); $fixer->configure($config); $method = new \ReflectionMethod($fixer, 'findHeaderCommentInsertionIndex'); $method->setAccessible(true); $this->assertSame($expected, $method->invoke($fixer, $tokens)); }
/** * @dataProvider provideCases */ public function testCountArguments($code, $openIndex, $closeIndex, $argumentsCount) { $mock = new AccessibleObject($this->getMockForAbstractClass('\\PhpCsFixer\\AbstractFunctionReferenceFixer')); $this->assertSame($argumentsCount, $mock->countArguments(Tokens::fromCode($code), $openIndex, $closeIndex)); }
/** * @param string $source * @param int[] $indexes * @param Token[] $expected */ private function doTestClearTokens($source, array $indexes, array $expected) { Tokens::clearCache(); $tokens = Tokens::fromCode($source); foreach ($indexes as $index) { $tokens->clearTokenAndMergeSurroundingWhitespace($index); } $this->assertSame(count($expected), $tokens->count()); foreach ($expected as $index => $expectedToken) { $token = $tokens[$index]; $expectedPrototype = $expectedToken->getPrototype(); if (is_array($expectedPrototype)) { unset($expectedPrototype[2]); // don't compare token lines as our token mutations don't deal with line numbers } $this->assertTrue($token->equals($expectedPrototype), sprintf('The token at index %d should be %s, got %s', $index, json_encode($expectedPrototype), $token->toJson())); } }
/** * Tests if a fixer fixes a given string to match the expected result. * * It is used both if you want to test if something is fixed or if it is not touched by the fixer. * It also makes sure that the expected output does not change when run through the fixer. That means that you * do not need two test cases like [$expected] and [$expected, $input] (where $expected is the same in both cases) * as the latter covers both of them. * This method throws an exception if $expected and $input are equal to prevent test cases that accidentally do * not test anything. * * @param string $expected The expected fixer output. * @param string|null $input The fixer input, or null if it should intentionally be equal to the output. * @param \SplFileInfo|null $file The file to fix, or null if unneeded. * @param FixerInterface|null $fixer The fixer to be used, or null if it should be inferred from the test name. */ protected function doTest($expected, $input = null, \SplFileInfo $file = null, FixerInterface $fixer = null) { if ($expected === $input) { throw new \InvalidArgumentException('Input parameter must not be equal to expected parameter.'); } $fixer = $fixer ?: $this->getFixer(); $file = $file ?: $this->getTestFile(); $fileIsSupported = $fixer->supports($file); if (null !== $input) { $this->assertNull($this->lintSource($input)); Tokens::clearCache(); $tokens = Tokens::fromCode($input); if ($fileIsSupported) { $this->assertTrue($fixer->isCandidate($tokens), 'Fixer must be a candidate for input code.'); $fixResult = $fixer->fix($file, $tokens); $this->assertNull($fixResult, '->fix method must return null.'); } $this->assertTrue($tokens->isChanged(), 'Tokens collection built on input code must be marked as changed after fixing.'); $this->assertSame($expected, $tokens->generateCode(), 'Code build on input code must match expected code.'); Tokens::clearCache(); $expectedTokens = Tokens::fromCode($expected); $tokens->clearEmptyTokens(); $this->assertTokens($expectedTokens, $tokens); } $this->assertNull($this->lintSource($expected)); Tokens::clearCache(); $tokens = Tokens::fromCode($expected); if ($fileIsSupported) { $fixResult = $fixer->fix($file, $tokens); $this->assertNull($fixResult, '->fix method must return null.'); } $this->assertFalse($tokens->isChanged(), 'Tokens collection built on expected code must not be marked as changed after fixing.'); $this->assertSame($expected, $tokens->generateCode(), 'Code build on expected code must not change.'); }
/** * {@inheritdoc} */ public function fix(\SplFileInfo $file, Tokens $tokens) { $namespace = false; $namespaceIndex = 0; $namespaceEndIndex = 0; $classyName = null; $classyIndex = 0; foreach ($tokens as $index => $token) { if ($token->isGivenKind(T_NAMESPACE)) { if (false !== $namespace) { return; } $namespaceIndex = $tokens->getNextMeaningfulToken($index); $namespaceEndIndex = $tokens->getNextTokenOfKind($index, array(';')); $namespace = trim($tokens->generatePartialCode($namespaceIndex, $namespaceEndIndex - 1)); } elseif ($token->isClassy()) { if (null !== $classyName) { return; } $classyIndex = $tokens->getNextMeaningfulToken($index); $classyName = $tokens[$classyIndex]->getContent(); } } if (null === $classyName) { return; } if (false !== $namespace) { $normNamespace = str_replace('\\', '/', $namespace); $path = str_replace('\\', '/', $file->getRealPath()); $dir = dirname($path); if (isset($this->configuration['dir'])) { $dir = substr($dir, strlen(realpath($this->configuration['dir'])) + 1); if (false === $dir) { $dir = ''; } if (strlen($normNamespace) > strlen($dir)) { if ('' !== $dir) { $normNamespace = substr($normNamespace, -strlen($dir)); } else { $normNamespace = ''; } } } $dir = substr($dir, -strlen($normNamespace)); if (false === $dir) { $dir = ''; } $filename = basename($path, '.php'); if ($classyName !== $filename) { $tokens[$classyIndex]->setContent($filename); } if ($normNamespace !== $dir && strtolower($normNamespace) === strtolower($dir)) { for ($i = $namespaceIndex; $i <= $namespaceEndIndex; ++$i) { $tokens[$i]->clear(); } $namespace = substr($namespace, 0, -strlen($dir)) . str_replace('/', '\\', $dir); $newNamespace = Tokens::fromCode('<?php namespace ' . $namespace . ';'); $newNamespace[0]->clear(); $newNamespace[1]->clear(); $newNamespace[2]->clear(); $newNamespace->clearEmptyTokens(); $tokens->insertAt($namespaceIndex, $newNamespace); } } else { $normClass = str_replace('_', '/', $classyName); $path = str_replace('\\', '/', $file->getRealPath()); $filename = substr($path, -strlen($normClass) - 4, -4); if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) { $tokens[$classyIndex]->setContent(str_replace('/', '_', $filename)); } } }
/** * @param OutputInterface $output * @param string $name */ private function describeRule(OutputInterface $output, $name) { $fixers = $this->getFixers(); if (!isset($fixers[$name])) { throw new DescribeNameNotFoundException($name, 'rule'); } /** @var FixerInterface $fixer */ $fixer = $fixers[$name]; if ($fixer instanceof DefinedFixerInterface) { $definition = $fixer->getDefinition(); } else { $definition = new ShortFixerDefinition('Description is not availble.'); } $output->writeln(sprintf('<info>Description of</info> %s <info>rule</info>.', $name)); $output->writeln($definition->getSummary()); if ($definition->getDescription()) { $output->writeln($definition->getDescription()); } $output->writeln(''); if ($fixer->isRisky()) { $output->writeln('<error>Fixer applying this rule is risky.</error>'); if ($definition->getRiskyDescription()) { $output->writeln($definition->getRiskyDescription()); } $output->writeln(''); } if ($fixer instanceof ConfigurableFixerInterface) { $output->writeln('<comment>Fixer is configurable.</comment>'); if ($definition->getConfigurationDescription()) { $output->writeln($definition->getConfigurationDescription()); } if ($definition->getDefaultConfiguration()) { $output->writeln(sprintf('Default configuration: <comment>%s</comment>.', $this->arrayToText($definition->getDefaultConfiguration()))); } $output->writeln(''); } if ($definition->getCodeSamples()) { $output->writeln('Fixing examples:'); $differ = new SebastianBergmannDiffer(); $diffFormatter = new DiffConsoleFormatter($output->isDecorated(), sprintf('<comment> ---------- begin diff ----------</comment>%s%%s%s<comment> ----------- end diff -----------</comment>', PHP_EOL, PHP_EOL)); foreach ($definition->getCodeSamples() as $index => $codeSample) { $old = $codeSample->getCode(); $tokens = Tokens::fromCode($old); if ($fixer instanceof ConfigurableFixerInterface) { $fixer->configure($codeSample->getConfiguration()); } $fixer->fix(new StdinFileInfo(), $tokens); $new = $tokens->generateCode(); $diff = $differ->diff($old, $new); if (null === $codeSample->getConfiguration()) { $output->writeln(sprintf(' * Example #%d.', $index + 1)); } else { $output->writeln(sprintf(' * Example #%d. Fixing with configuration: <comment>%s</comment>.', $index + 1, $this->arrayToText($codeSample->getConfiguration()))); } $output->writeln($diffFormatter->format($diff, ' %s')); $output->writeln(''); } } if ($definition instanceof ShortFixerDefinition) { $output->writeln(sprintf('<question>This rule is not yet described, do you want to help us and describe it?</question>')); $output->writeln('Contribute at <comment>https://github.com/FriendsOfPHP/PHP-CS-Fixer</comment> !'); $output->writeln(''); } }
public function fixFile(\SplFileInfo $file, array $fixers, $dryRun, $diff, FileCacheManager $fileCacheManager) { $new = $old = file_get_contents($file->getRealPath()); $name = $this->getFileRelativePathname($file); if ('' === $old || !$fileCacheManager->needFixing($name, $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; } 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, $name)); return; } $appliedFixers = array(); // 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(); try { foreach ($fixers as $fixer) { if (!$fixer->supports($file) || !$fixer->isCandidate($tokens)) { continue; } $fixer->fix($file, $tokens); if ($tokens->isChanged()) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); $appliedFixers[] = $fixer->getName(); } } } catch (\Exception $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_EXCEPTION)); $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name)); return; } $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, $name)); return; } 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); if ($diff) { $fixInfo['diff'] = $this->stringDiff($old, $new); } } $fileCacheManager->setFile($name, $new); $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES)); return $fixInfo; }
private function fixGroupUse(Tokens $tokens, $index, $endIndex) { list($groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment) = $this->getGroupDeclaration($tokens, $index); $statements = $this->getGroupStatements($tokens, $groupPrefix, $groupOpenIndex, $groupCloseIndex, $comment); if (count($statements) < 2) { return; } $tokens->clearRange($index, $groupCloseIndex); if ($tokens[$endIndex]->equals(';')) { $tokens[$endIndex]->clear(); } $ending = $this->whitespacesConfig->getLineEnding(); $importTokens = Tokens::fromCode('<?php ' . implode($ending, $statements)); $importTokens[0]->clear(); $importTokens->clearEmptyTokens(); $tokens->insertAt($index, $importTokens); }
/** * @dataProvider provideGetFunctionProperties */ public function testGetFunctionProperties($source, $index, $expected) { $tokens = Tokens::fromCode($source); $tokensAnalyzer = new TokensAnalyzer($tokens); $attributes = $tokensAnalyzer->getMethodAttributes($index); $this->assertSame($expected, $attributes); }
/** * @dataProvider getImportUseIndexesCasesPHP70 * @requires PHP 7.0 */ public function testGetImportUseIndexesPHP70(array $expected, $input, $perNamespace = false) { $tokens = Tokens::fromCode($input); $tokensAnalyzer = new TokensAnalyzer($tokens); $this->assertSame($expected, $tokensAnalyzer->getImportUseIndexes($perNamespace)); }
public function testFindGivenKind() { $source = <<<'PHP' <?php class FooBar { public function foo() { return 'bar'; } public function bar() { return 'foo'; } } PHP; $tokens = Tokens::fromCode($source); /** @var Token[] $found */ $found = $tokens->findGivenKind(T_CLASS); $this->assertTrue(is_array($found)); $this->assertCount(1, $found); $this->assertArrayHasKey(1, $found); $this->assertSame(T_CLASS, $found[1]->getId()); /** @var array $found */ $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION)); $this->assertCount(2, $found); $this->assertArrayHasKey(T_CLASS, $found); $this->assertTrue(is_array($found[T_CLASS])); $this->assertCount(1, $found[T_CLASS]); $this->assertArrayHasKey(1, $found[T_CLASS]); $this->assertSame(T_CLASS, $found[T_CLASS][1]->getId()); $this->assertArrayHasKey(T_FUNCTION, $found); $this->assertTrue(is_array($found[T_FUNCTION])); $this->assertCount(2, $found[T_FUNCTION]); $this->assertArrayHasKey(9, $found[T_FUNCTION]); $this->assertSame(T_FUNCTION, $found[T_FUNCTION][9]->getId()); $this->assertArrayHasKey(26, $found[T_FUNCTION]); $this->assertSame(T_FUNCTION, $found[T_FUNCTION][26]->getId()); // test offset and limits of the search $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 10); $this->assertCount(0, $found[T_CLASS]); $this->assertCount(1, $found[T_FUNCTION]); $this->assertArrayHasKey(26, $found[T_FUNCTION]); $found = $tokens->findGivenKind(array(T_CLASS, T_FUNCTION), 2, 10); $this->assertCount(0, $found[T_CLASS]); $this->assertCount(1, $found[T_FUNCTION]); $this->assertArrayHasKey(9, $found[T_FUNCTION]); }
/** * Tests if a fixer fixes a given string to match the expected result. * * It is used both if you want to test if something is fixed or if it is not touched by the fixer. * It also makes sure that the expected output does not change when run through the fixer. That means that you * do not need two test cases like [$expected] and [$expected, $input] (where $expected is the same in both cases) * as the latter covers both of them. * This method throws an exception if $expected and $input are equal to prevent test cases that accidentally do * not test anything. * * @param string $expected The expected fixer output * @param string|null $input The fixer input, or null if it should intentionally be equal to the output * @param \SplFileInfo|null $file The file to fix, or null if unneeded * @param FixerInterface|null $fixer The fixer to be used, or null if it should be inferred from the test name */ protected function doTest($expected, $input = null, \SplFileInfo $file = null, FixerInterface $fixer = null) { if ($expected === $input) { throw new \InvalidArgumentException('Input parameter must not be equal to expected parameter.'); } $fixer = $fixer ?: $this->getFixer(); $file = $file ?: $this->getTestFile(); $fileIsSupported = $fixer->supports($file); if (null !== $input) { $this->assertNull($this->lintSource($input)); Tokens::clearCache(); $tokens = Tokens::fromCode($input); if ($fileIsSupported) { $this->assertTrue($fixer->isCandidate($tokens), 'Fixer must be a candidate for input code.'); $fixResult = $fixer->fix($file, $tokens); $this->assertNull($fixResult, '->fix method must return null.'); } $this->assertSame($expected, $tokens->generateCode(), 'Code build on input code must match expected code.'); $this->assertTrue($tokens->isChanged(), 'Tokens collection built on input code must be marked as changed after fixing.'); $tokens->clearEmptyTokens(); $this->assertSame(count($tokens), count(array_unique(array_map(function (Token $token) { return spl_object_hash($token); }, $tokens->toArray()))), 'Token items inside Tokens collection must be unique.'); Tokens::clearCache(); $expectedTokens = Tokens::fromCode($expected); $this->assertTokens($expectedTokens, $tokens); } $this->assertNull($this->lintSource($expected)); Tokens::clearCache(); $tokens = Tokens::fromCode($expected); $isCandidate = $fixer->isCandidate($tokens); $this->assertFalse($tokens->isChanged(), 'Fixer should not touch Tokens on candidate check.'); if (!$isCandidate) { return; } if ($fileIsSupported) { $fixResult = $fixer->fix($file, $tokens); $this->assertNull($fixResult, '->fix method must return null.'); } $this->assertSame($expected, $tokens->generateCode(), 'Code build on expected code must not change.'); $this->assertFalse($tokens->isChanged(), 'Tokens collection built on expected code must not be marked as changed after fixing.'); }
private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult) { $name = $this->getFileRelativePathname($file); try { $lintingResult->check(); } catch (LintingException $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_INVALID)); $this->errorsManager->report(new Error(Error::TYPE_INVALID, $name)); return; } $fixers = $this->config->getFixers(); $old = file_get_contents($file->getRealPath()); $tokens = Tokens::fromCode($old); $oldHash = $tokens->getCodeHash(); $newHash = $oldHash; $new = $old; $appliedFixers = array(); try { foreach ($fixers as $fixer) { if (!$fixer->supports($file) || !$fixer->isCandidate($tokens)) { continue; } $fixer->fix($file, $tokens); if ($tokens->isChanged()) { $tokens->clearEmptyTokens(); $tokens->clearChanged(); $appliedFixers[] = $fixer->getName(); } } } catch (\Exception $e) { $this->processException($name); return; } catch (\ParseError $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_LINT)); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name)); return; } catch (\Throwable $e) { $this->processException($name); return; } $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)->check(); } catch (LintingException $e) { $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus(FixerFileProcessedEvent::STATUS_LINT)); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name)); return; } if (!$this->isDryRun) { if (false === @file_put_contents($file->getRealPath(), $new)) { $error = error_get_last(); if (null !== $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, 'diff' => $this->differ->diff($old, $new)); } $this->cacheManager->setFile($name, $new); $this->dispatchEvent(FixerFileProcessedEvent::NAME, FixerFileProcessedEvent::create()->setStatus($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES)); return $fixInfo; }
/** * @param string $source valid PHP source code * @param int $startIndex start index of the comment block * @param int $endIndex expected index of the last token of the block * @param bool $isEmpty expected value of empty flag returned * * @dataProvider provideCommentBlockCases */ public function testGetCommentBlock($source, $startIndex, $endIndex, $isEmpty) { Tokens::clearCache(); $tokens = Tokens::fromCode($source); $this->assertTrue($tokens[$startIndex]->isComment(), sprintf('Misconfiguration of test, expected comment token at index "%d".', $startIndex)); $fixer = $this->getFixer(); $method = new \ReflectionMethod($fixer, 'getCommentBlock'); $method->setAccessible(true); list($foundStart, $foundEnd, $foundIsEmpty) = $method->invoke($fixer, $tokens, $startIndex); $this->assertSame($startIndex, $foundStart, 'Find start index of block failed.'); $this->assertSame($endIndex, $foundEnd, 'Find end index of block failed.'); $this->assertSame($isEmpty, $foundIsEmpty, 'Is empty comment block detection failed.'); }