Merge pull request #3387 from rectorphp/scoper-test

[compiler] Add ScoperTest
This commit is contained in:
kodiakhq[bot] 2020-05-20 11:13:19 +00:00 committed by GitHub
commit bc1d391cc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 270 additions and 164 deletions

View File

@ -45,5 +45,5 @@ jobs:
- name: Setup Problem Matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- run: composer update --no-progress --prefer-lowest
- run: composer update --no-progress # --prefer-lowest
- run: vendor/bin/phpunit

View File

@ -3,24 +3,11 @@
declare(strict_types = 1);
use Psr\Container\ContainerInterface;
use Rector\Compiler\Console\RectorCompilerConsoleApplication;
use Rector\Compiler\HttpKernel\RectorCompilerKernel;
use Rector\Compiler\DependencyInjection\ContainerFactory;
require_once __DIR__ . '/../vendor/autoload.php';
final class ContainerFactory
{
public function create(): ContainerInterface
{
$environment = 'prod' . random_int(1, 10000000);
$rectorCompilerKernel = new RectorCompilerKernel($environment, true);
$rectorCompilerKernel->boot();
return $rectorCompilerKernel->getContainer();
}
}
$containerFactory = new ContainerFactory();
$container = $containerFactory->create();

View File

@ -7,71 +7,13 @@ declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use Isolated\Symfony\Component\Finder\Finder;
use Nette\Neon\Neon;
final class WhitelistedStubsProvider
{
/**
* @return string[]
*/
public function provide(): array
{
$stubs = [
// @see https://github.com/rectorphp/rector/issues/2852#issuecomment-586315588
'../../vendor/hoa/consistency/Prelude.php',
];
// mirrors https://github.com/phpstan/phpstan-src/commit/04f777bc4445725d17dac65c989400485454b145
$stubsDirectory = __DIR__ . '/../../vendor/jetbrains/phpstorm-stubs';
if (file_exists($stubsDirectory)) {
$stubFinder = Finder::create()
->files()
->name('*.php')
->in($stubsDirectory)
->notName('#PhpStormStubsMap\.php$#');
foreach ($stubFinder->getIterator() as $fileInfo) {
/** @var SplFileInfo $fileInfo */
$stubs[] = $fileInfo->getPathName();
}
}
return $stubs;
}
}
use Nette\Utils\Strings;
use Rector\Compiler\PhpScoper\StaticEasyPrefixer;
use Rector\Compiler\PhpScoper\WhitelistedStubsProvider;
$whitelistedStubsProvider = new WhitelistedStubsProvider();
final class EasyPrefixer
{
/**
* @var string[]
*/
public const ALLOWED_PREFIXES = ['Hoa\*', 'PhpParser\*', 'PHPStan\*', 'Rector\*'];
public static function prefixClass(string $class, string $prefix): string
{
// @todo move to allowed prefixes
if (strpos($class, 'PHPStan\\') === 0) {
return $class;
}
if (strpos($class, 'PhpParser\\') === 0) {
return $class;
}
if (strpos($class, 'Rector\\') === 0) {
return $class;
}
if (strpos($class, 'Hoa\\') === 0) {
return $class;
}
if (strpos($class, '@') === 0) {
return $class;
}
return $prefix . '\\' . $class;
}
}
return [
'prefix' => null,
'finders' => [],
@ -81,12 +23,14 @@ return [
if ($filePath !== 'bin/rector') {
return $content;
}
return str_replace('__DIR__ . \'/..', '\'phar://rector.phar', $content);
return str_replace("__DIR__ . '/..", "'phar://rector.phar", $content);
},
function (string $filePath, string $prefix, string $content): string {
if ($filePath !== 'vendor/nette/di/src/DI/Compiler.php') {
return $content;
}
return str_replace(
'|Nette\\\\DI\\\\Statement',
sprintf('|\\\\%s\\\\Nette\\\\DI\\\\Statement', $prefix),
@ -97,7 +41,7 @@ return [
if ($filePath !== 'vendor/nette/di/src/DI/Config/DefinitionSchema.php') {
return $content;
}
$content = str_replace(sprintf('\'%s\\\\callable', $prefix), '\'callable', $content);
$content = str_replace(sprintf('\'%s\\\\callable', $prefix), "'callable", $content);
return str_replace(
'|Nette\\\\DI\\\\Definitions\\\\Statement',
sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix),
@ -108,7 +52,8 @@ return [
if ($filePath !== 'vendor/nette/di/src/DI/Extensions/ExtensionsExtension.php') {
return $content;
}
$content = str_replace(sprintf('\'%s\\\\string', $prefix), '\'string', $content);
$content = str_replace(sprintf('\'%s\\\\string', $prefix), "'string", $content);
return str_replace(
'|Nette\\\\DI\\\\Definitions\\\\Statement',
sprintf('|%s\\\\Nette\\\\DI\\\\Definitions\\\\Statement', $prefix),
@ -119,6 +64,7 @@ return [
if ($filePath !== 'src/Testing/TestCase.php') {
return $content;
}
return str_replace(
sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix),
'\\PHPUnit\\Framework\\TestCase',
@ -129,6 +75,7 @@ return [
if ($filePath !== 'src/Testing/LevelsTestCase.php') {
return $content;
}
return str_replace(
[
sprintf('\\%s\\PHPUnit\\Framework\\AssertionFailedError', $prefix),
@ -139,25 +86,24 @@ return [
);
},
// unprefix configuraion in sets, @see https://github.com/rectorphp/rector/issues/3227
function (string $filePath, string $prefix, string $content): string {
// only *.yaml files
if (strpos($filePath, '.yaml') === false) {
if (! Strings::endsWith($filePath, '.yaml')) {
return $content;
}
// @see https://github.com/rectorphp/rector/issues/3227
if (strpos($filePath, 'config/set/') !== 0) {
if (! Strings::startsWith($filePath, 'config/set/')) {
return $content;
}
// @todo - prefix classes in yaml files?
return $content;
return StaticEasyPrefixer::unprefixQuotedValues($prefix, $content);
},
// mimics https://github.com/phpstan/phpstan-src/commit/5a6a22e5c4d38402c8cc888d8732360941c33d43#diff-463a36e4a5687fb2366b5ee56cdad92d
function (string $filePath, string $prefix, string $content): string {
// only *.neon files
if (strpos($filePath, '.neon') === false) {
if (! Strings::endsWith($filePath, '.neon')) {
return $content;
}
@ -171,16 +117,16 @@ return [
if (array_key_exists('services', $neon)) {
foreach ($neon['services'] as $key => $service) {
if (array_key_exists('class', $service) && is_string($service['class'])) {
$service['class'] = EasyPrefixer::prefixClass($service['class'], $prefix);
$service['class'] = StaticEasyPrefixer::prefixClass($service['class'], $prefix);
}
if (array_key_exists('factory', $service) && is_string($service['factory'])) {
$service['factory'] = EasyPrefixer::prefixClass($service['factory'], $prefix);
$service['factory'] = StaticEasyPrefixer::prefixClass($service['factory'], $prefix);
}
if (array_key_exists('autowired', $service) && is_array($service['autowired'])) {
foreach ($service['autowired'] as $i => $autowiredName) {
$service['autowired'][$i] = EasyPrefixer::prefixClass($autowiredName, $prefix);
$service['autowired'][$i] = StaticEasyPrefixer::prefixClass($autowiredName, $prefix);
}
}
@ -188,17 +134,24 @@ return [
}
}
return Neon::encode($updatedNeon, Neon::BLOCK);
$updatedContent = Neon::encode($updatedNeon, Neon::BLOCK);
// default indent is tab, we have spaces
return Strings::replace($updatedContent, '#\t#', ' ');
},
// mimics https://github.com/phpstan/phpstan-src/commit/fd8f0a852207a1724ae4a262f47d9a449de70da4#diff-463a36e4a5687fb2366b5ee56cdad92d
function (string $filePath, string $prefix, string $content): string {
if (strpos($filePath, 'src/') !== 0) {
if (! Strings::contains($filePath, 'src/')) {
return $content;
}
$content = str_replace(sprintf('\'%s\\\\r\\\\n\'', $prefix), '\'\\\\r\\\\n\'', $content);
return str_replace(sprintf('\'%s\\\\', $prefix), '\'', $content);
if (Strings::startsWith($filePath, 'src/')) {
return $content;
}
return StaticEasyPrefixer::unprefixQuotedValues($prefix, $content);
},
],
'whitelist' => EasyPrefixer::ALLOWED_PREFIXES,
'whitelist' => StaticEasyPrefixer::EXCLUDED_NAMESPACES,
];

View File

@ -5,20 +5,31 @@
"require": {
"php": "^7.2",
"nette/neon": "^3.0",
"nette/utils": "^3.0",
"ondram/ci-detector": "^3.3",
"nette/utils": "^3.1",
"ondram/ci-detector": "^3.4",
"sebastian/diff": "^3.0|^4.0",
"symfony/console": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/filesystem": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symplify/console-color-diff": "^7.3",
"symplify/auto-bind-parameter": "^7.3",
"symplify/package-builder": "^7.3"
"symplify/console-color-diff": "^8.0",
"symplify/auto-bind-parameter": "^8.0",
"symplify/smart-file-system": "^8.0",
"symplify/package-builder": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5|^9.0"
},
"autoload": {
"psr-4": {
"Rector\\Compiler\\": "src"
}
}
},
"autoload-dev": {
"psr-4": {
"Rector\\Compiler\\Tests\\": "tests"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -11,7 +11,9 @@ services:
resource: '../src'
exclude:
- '../src/Exception/*'
- '../src/DependencyInjection/*'
- '../src/HttpKernel/*'
- '../src/PhpScoper/*'
Symfony\Component\Filesystem\Filesystem: null

19
compiler/phpunit.xml Normal file
View File

@ -0,0 +1,19 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
>
<testsuites>
<testsuite name="main">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="false">
<directory>src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -6,7 +6,6 @@ namespace Rector\Compiler\Console\Command;
use OndraM\CiDetector\CiDetector;
use Rector\Compiler\Composer\ComposerJsonManipulator;
use Rector\Compiler\Debug\FileLister;
use Rector\Compiler\Renaming\JetbrainsStubsRenamer;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@ -45,11 +44,6 @@ final class CompileCommand extends Command
*/
private $jetbrainsStubsRenamer;
/**
* @var FileLister
*/
private $fileLister;
/**
* @var CiDetector
*/
@ -61,7 +55,6 @@ final class CompileCommand extends Command
ComposerJsonManipulator $composerJsonManipulator,
SymfonyStyle $symfonyStyle,
JetbrainsStubsRenamer $jetbrainsStubsRenamer,
FileLister $fileLister,
CiDetector $ciDetector
) {
$this->dataDir = $dataDir;
@ -69,7 +62,6 @@ final class CompileCommand extends Command
$this->composerJsonManipulator = $composerJsonManipulator;
$this->jetbrainsStubsRenamer = $jetbrainsStubsRenamer;
$this->fileLister = $fileLister;
$this->symfonyStyle = $symfonyStyle;
$this->ciDetector = $ciDetector;
@ -84,11 +76,15 @@ final class CompileCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output): int
{
// 1.
$composerJsonFile = $this->buildDir . '/composer.json';
$this->symfonyStyle->section('Loading and updating ' . $composerJsonFile);
$this->symfonyStyle->title('1. Adding "phpstan/phpstan-src" to ' . $composerJsonFile);
$this->composerJsonManipulator->fixComposerJson($composerJsonFile);
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->title('2. Running "composer update" without dev');
$process = new Process([
'composer',
'update',
@ -98,31 +94,32 @@ final class CompileCommand extends Command
'--classmap-authoritative',
'--ansi',
], $this->buildDir, null, null, null);
$process->mustRun(static function (string $type, string $buffer) use ($output): void {
$output->write($buffer);
});
// debug
if (file_exists($this->buildDir . '/vendor/jetbrains')) {
$this->fileLister->listFilesInDirectory($this->buildDir . '/vendor/jetbrains');
}
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->title('3. Renaming PHPStorm stubs from "*.php" to ".stub"');
// 2.
$this->symfonyStyle->section('Renaming PHPStorm stubs from "*.php" to ".stub"');
$this->jetbrainsStubsRenamer->renamePhpStormStubs($this->buildDir);
// 3.
$this->symfonyStyle->newLine(2);
// the '--no-parallel' is needed, so "scoper.php.inc" can "require __DIR__ ./vendor/autoload.php"
// and "Nette\Neon\Neon" class can be used there
$this->symfonyStyle->section('Packing and prefixing rector.phar with Box and PHP Scoper');
$this->symfonyStyle->title('4. Packing and prefixing rector.phar with Box and PHP Scoper');
$process = new Process(['php', 'box.phar', 'compile', '--no-parallel'], $this->dataDir, null, null, null);
$process->mustRun(static function (string $type, string $buffer) use ($output): void {
$output->write($buffer);
});
// 4.
$this->symfonyStyle->section('Restoring root composer.json with "require-dev"');
$this->symfonyStyle->note('You still need to run "composer update" to install those dependencies');
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->title('5. Restoring root composer.json with "require-dev"');
$this->composerJsonManipulator->restoreComposerJson($composerJsonFile);

View File

@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Compiler\Debug;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
final class FileLister
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
public function __construct(SymfonyStyle $symfonyStyle)
{
$this->symfonyStyle = $symfonyStyle;
}
public function listFilesInDirectory(string $directory): void
{
$finder = (new Finder())
->in($directory)
->name('*.php')
->files();
/** @var SplFileInfo[] $phpFileInfos */
$phpFileInfos = $finder->getIterator();
$this->symfonyStyle->newLine();
$this->symfonyStyle->section(sprintf('Files found in "%s" directory', $directory));
foreach ($phpFileInfos as $phpFileInfo) {
$this->symfonyStyle->writeln($phpFileInfo->getRelativePathname());
}
$this->symfonyStyle->newLine();
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Rector\Compiler\DependencyInjection;
use Psr\Container\ContainerInterface;
use Rector\Compiler\HttpKernel\RectorCompilerKernel;
final class ContainerFactory
{
public function create(): ContainerInterface
{
$environment = 'prod' . random_int(1, 10000000);
$rectorCompilerKernel = new RectorCompilerKernel($environment, true);
$rectorCompilerKernel->boot();
return $rectorCompilerKernel->getContainer();
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Rector\Compiler\PhpScoper;
use Nette\Utils\Strings;
final class StaticEasyPrefixer
{
/**
* @var string[]
*/
public const EXCLUDED_NAMESPACES = ['Hoa\*', 'PhpParser\*', 'PHPStan\*', 'Rector\*'];
public static function prefixClass(string $class, string $prefix): string
{
foreach (self::EXCLUDED_NAMESPACES as $excludedNamespace) {
$excludedNamespace = Strings::substring($excludedNamespace, 0, -2) . '\\';
if (Strings::startsWith($class, $excludedNamespace)) {
return $class;
}
}
if (Strings::startsWith($class, '@')) {
return $class;
}
return $prefix . '\\' . $class;
}
public static function unprefixQuotedValues(string $prefix, string $content): string
{
$match = sprintf('\'%s\\\\r\\\\n\'', $prefix);
$content = Strings::replace($content, '#' . $match . '#', '\'\\\\r\\\\n\'');
$match = sprintf('\'%s\\\\', $prefix);
return Strings::replace($content, '#' . $match . '#', "'");
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Compiler\PhpScoper;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
final class WhitelistedStubsProvider
{
/**
* @return string[]
*/
public function provide(): array
{
$stubs = [
// @see https://github.com/rectorphp/rector/issues/2852#issuecomment-586315588
'../../../vendor/hoa/consistency/Prelude.php',
];
// mirrors https://github.com/phpstan/phpstan-src/commit/04f777bc4445725d17dac65c989400485454b145
$stubsDirectory = __DIR__ . '/../../../vendor/jetbrains/phpstorm-stubs';
if (file_exists($stubsDirectory)) {
$stubFinder = Finder::create()
->files()
->name('*.php')
->in($stubsDirectory)
->notName('#PhpStormStubsMap\.php$#');
foreach ($stubFinder->getIterator() as $fileInfo) {
/** @var SplFileInfo $fileInfo */
$stubs[] = $fileInfo->getPathName();
}
}
return $stubs;
}
}

View File

@ -0,0 +1,9 @@
services:
Rector\Core\Rector\Function_\FunctionToStaticCallRector:
$functionToStaticCall:
file_get_contents: ['Nette\Utils\FileSystem', 'read']
-----
services:
Rector\Core\Rector\Function_\FunctionToStaticCallRector:
$functionToStaticCall:
file_get_contents: ['Nette\Utils\FileSystem', 'read']

View File

@ -0,0 +1,7 @@
services:
-
arguments: ['Prefix__\SomeClass\WithNesting']
-----
services:
-
arguments: ['SomeClass\WithNesting']

View File

@ -0,0 +1,7 @@
services:
-
class: SomeService
-----
services:
-
class: Prefix__\SomeService

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Rector\Compiler\Tests;
use Iterator;
use Nette\Utils\Strings;
use PHPUnit\Framework\TestCase;
use Symplify\PackageBuilder\Tests\StaticFixtureLoader;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ScopingTest extends TestCase
{
/**
* @var string
*/
private const PREFIX = 'Prefix__';
/**
* @var callable[]
*/
private $patcherCallbacks = [];
protected function setUp(): void
{
$scoper = require __DIR__ . '/../build/scoper.inc.php';
$this->patcherCallbacks = $scoper['patchers'];
}
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
[$content, $expectedContent] = Strings::split($fileInfo->getContents(), "#-----\n#");
foreach ($this->patcherCallbacks as $patcherCallback) {
$relativeFilePath = $fileInfo->getRelativeFilePathFromDirectory(__DIR__ . '/Fixture');
$content = $patcherCallback($relativeFilePath, self::PREFIX, $content);
}
// normalize end-line spaces
$expectedContent = rtrim($expectedContent);
$content = rtrim($content);
$this->assertSame($expectedContent, $content, $fileInfo->getRelativeFilePath());
}
public function provideData(): Iterator
{
return StaticFixtureLoader::loadFromDirectory(__DIR__ . '/Fixture');
}
}

View File

@ -20,7 +20,7 @@
"nette/robot-loader": "^3.2",
"nette/utils": "^3.1",
"nikic/php-parser": "^4.4",
"ondram/ci-detector": "^3.1",
"ondram/ci-detector": "^3.4",
"phpstan/phpdoc-parser": "^0.4.7",
"phpstan/phpstan": "^0.12.25",
"phpstan/phpstan-phpunit": "^0.12",
@ -203,7 +203,8 @@
"Rector\\Performance\\Tests\\": "rules/performance/tests",
"Rector\\Naming\\Tests\\": "rules/naming/tests",
"Rector\\Order\\Tests\\": "rules/order/tests",
"Rector\\MockistaToMockery\\Tests\\": "rules/mockista-to-mockery/tests"
"Rector\\MockistaToMockery\\Tests\\": "rules/mockista-to-mockery/tests",
"Rector\\Compiler\\Tests\\": "compiler/tests"
},
"classmap": [
"rules/cakephp/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Source",

View File

@ -15,6 +15,8 @@ parameters:
- 'tests'
- 'utils'
- 'compiler/src'
- 'compiler/bin/compile'
- 'compiler/build/scoper.inc.php'
autoload_paths:
- 'compiler/src'