Skip to content

Commit

Permalink
Add playground-runner (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
mglaman authored Nov 10, 2023
1 parent bde6445 commit 072ef31
Show file tree
Hide file tree
Showing 14 changed files with 16,156 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ tests export-ignore
.travis.yml export-ignore
build.xml export-ignore
phpcs.xml export-ignore
phpstan.neon export-ignore
phpstan.neon export-ignore

/playground-runner export-ignore
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "composer"
directory: "/playground-runner"
schedule:
interval: "weekly"
ignore:
- dependency-name: "drupal/core*"
update-types: ["version-update:semver-major"]
groups:
drupal-core:
patterns:
- "drupal/core"
- "drupal/core-dev"
- package-ecosystem: "npm"
directory: "/playground-runner"
schedule:
interval: "weekly"
43 changes: 43 additions & 0 deletions .github/workflows/deploy-playground-runner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "Deploy Playground Runner"
on:
push:
branches:
- main
paths:
- '.github/workflows/deploy-playground-runner.yml'
- 'playground-runner/**'
concurrency: api_build
jobs:
deploy:
name: "Build & deploy"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: "Install Node"
uses: actions/setup-node@v4
with:
node-version: "20"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.1"
- name: "npm ci"
working-directory: ./playground-runner
run: "npm ci"
- name: "composer install"
working-directory: ./playground-runner
run: "composer install --no-interaction --no-progress"
- name: "phpunit"
working-directory: ./playground-runner
run: "php vendor/bin/phpunit"
- name: "Deploy"
working-directory: ./playground-runner
env:
AWS_DEFAULT_REGION: "us-east-1"
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: "npm run deploy"
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
composer.phar
composer.lock
/composer.phar
/composer.lock
/vendor/
/clover.xml
.circleci/config_local.yml

# Fix PHPUnit compatibility mutated class.
/tests/fixtures/TestCase.php
/tests/fixtures/drupal/sites/simpletest/
.phpunit.result.cache
.idea/
3 changes: 3 additions & 0 deletions playground-runner/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor
/.serverless
/node_modules
7 changes: 7 additions & 0 deletions playground-runner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Test

```sh
composer install
npm ci
serverless bref:local -f analyze --path test-event.json
```
189 changes: 189 additions & 0 deletions playground-runner/analyze.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

declare(strict_types = 1);

use PHPStan\AnalysedCodeException;
use PHPStan\Analyser\Error;
use PHPStan\Analyser\RuleErrorTransformer;
use PHPStan\Analyser\ScopeContext;
use PHPStan\Analyser\ScopeFactory;
use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode;
use PHPStan\BetterReflection\Reflection\Exception\CircularReference;
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
use PHPStan\Collectors\CollectedData;
use PHPStan\Node\CollectedDataNode;
use Symfony\Component\Console\Formatter\OutputFormatter;

require __DIR__.'/vendor/autoload.php';

error_reporting(E_ALL);
ini_set('display_errors', '1');

$phpstanVersion = \Composer\InstalledVersions::getPrettyVersion('phpstan/phpstan');
$phpstanDrupalVersion = \Composer\InstalledVersions::getPrettyVersion('mglaman/phpstan-drupal');
$drupalCoreVersion = \Composer\InstalledVersions::getPrettyVersion('drupal/core');

/**
* @param CollectedData[] $collectedData
* @return Error[]
*/
function getCollectedDataErrors(\PHPStan\DependencyInjection\Container $container, array $collectedData): array
{
$nodeType = CollectedDataNode::class;
$node = new CollectedDataNode($collectedData, true);
$file = 'N/A';
$scope = $container->getByType(ScopeFactory::class)->create(ScopeContext::create($file));
$ruleRegistry = $container->getByType(\PHPStan\Rules\Registry::class);
$ruleErrorTransformer = $container->getByType(RuleErrorTransformer::class);
$errors = [];
foreach ($ruleRegistry->getRules($nodeType) as $rule) {
try {
$ruleErrors = $rule->processNode($node, $scope);
} catch (AnalysedCodeException $e) {
$errors[] = new Error($e->getMessage(), $file, $node->getLine(), $e, null, null, $e->getTip());
continue;
} catch (IdentifierNotFound $e) {
$errors[] = new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols');
continue;
} catch (UnableToCompileNode | CircularReference $e) {
$errors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getLine(), $e);
continue;
}

foreach ($ruleErrors as $ruleError) {
$errors[] = $ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getLine());
}
}

return $errors;
}

function clearTemp($tmpDir): void
{
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($tmpDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($files as $fileinfo) {
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$todo($fileinfo->getRealPath());
}
}

return function(array $event) use ($phpstanVersion, $phpstanDrupalVersion, $drupalCoreVersion) {
$tmpDir = sys_get_temp_dir() . '/phpstan-runner';
if (!is_dir($tmpDir)) {
mkdir($tmpDir);
}
clearTemp($tmpDir);
$code = $event['code'];
$level = $event['level'];
$codePath = sys_get_temp_dir() . '/phpstan-runner/tmp.php';
file_put_contents($codePath, $code);

$rootDir = __DIR__;
$configFiles = [
$rootDir . '/playground.neon',
$rootDir . '/vendor/mglaman/phpstan-drupal/extension.neon',
$rootDir . '/vendor/mglaman/phpstan-drupal/rules.neon',
$rootDir . '/vendor/phpstan/phpstan-deprecation-rules/rules.neon',
];
foreach ([
'strictRules' => $rootDir . '/vendor/phpstan/phpstan-strict-rules/rules.neon',
'bleedingEdge' => 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/conf/bleedingEdge.neon',
] as $key => $file) {
if (!isset($event[$key]) || !$event[$key]) {
continue;
}

$configFiles[] = $file;
}
$finalConfigFile = $tmpDir . '/run-phpstan-tmp.neon';
$neon = \Nette\Neon\Neon::encode([
'includes' => $configFiles,
'parameters' => [
'inferPrivatePropertyTypeFromConstructor' => true,
'treatPhpDocTypesAsCertain' => $event['treatPhpDocTypesAsCertain'] ?? true,
'phpVersion' => $event['phpVersion'] ?? 80000,
'featureToggles' => [
'disableRuntimeReflectionProvider' => true,
],
'drupal' => [
'drupal_root' => \Composer\InstalledVersions::getInstallPath('drupal/core'),
],
],
'services' => [
'currentPhpVersionSimpleParser!' => [
'factory' => '@currentPhpVersionRichParser',
],
],
]);
file_put_contents($finalConfigFile, $neon);

require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionUnionType.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionIntersectionType.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionAttribute.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Attribute.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/UnitEnum.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/BackedEnum.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnum.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnumUnitCase.php';
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnumBackedCase.php';

$containerFactory = new \PHPStan\DependencyInjection\ContainerFactory($tmpDir);
$container = $containerFactory->create($tmpDir, [sprintf('%s/config.level%s.neon', $containerFactory->getConfigDirectory(), $level), $finalConfigFile], [$codePath]);

// Note: this is the big change from the parent script in phpstan-src.
foreach ($container->getParameter('bootstrapFiles') as $bootstrapFileFromArray) {
try {
(static function (string $bootstrapFileFromArray) use ($container): void {
require_once $bootstrapFileFromArray;
})($bootstrapFileFromArray);
} catch (Throwable $e) {
$error = sprintf('%s thrown in %s on line %d while loading bootstrap file %s: %s', get_class($e), $e->getFile(), $e->getLine(), $file, $e->getMessage());
return ['result' => [$error], 'versions' => [
'phpstan' => $phpstanVersion,
'phpstan-drupal' => $phpstanDrupalVersion,
'drupal' => $drupalCoreVersion,
]];
}
}

/** @var \PHPStan\Analyser\Analyser $analyser */
$analyser = $container->getByType(\PHPStan\Analyser\Analyser::class);
$analyserResult = $analyser->analyse([$codePath], null, null, false, [$codePath]);
$hasInternalErrors = count($analyserResult->getInternalErrors()) > 0 || $analyserResult->hasReachedInternalErrorsCountLimit();
$results = $analyserResult->getErrors();

if (!$hasInternalErrors) {
foreach (getCollectedDataErrors($container, $analyserResult->getCollectedData()) as $error) {
$results[] = $error;
}
}

error_clear_last();

$errors = [];
$tipFormatter = new OutputFormatter(false);
foreach ($results as $result) {
$error = [
'message' => $result->getMessage(),
'line' => $result->getLine(),
'ignorable' => $result->canBeIgnored(),
];
if ($result->getTip() !== null) {
$error['tip'] = $tipFormatter->format($result->getTip());
}
if ($result->getIdentifier() !== null) {
$error['identifier'] = $result->getIdentifier();
}
$errors[] = $error;
}

return ['result' => $errors, 'versions' => [
'phpstan' => $phpstanVersion,
'phpstan-drupal' => $phpstanDrupalVersion,
'drupal' => $drupalCoreVersion,
]];
};
20 changes: 20 additions & 0 deletions playground-runner/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"require": {
"php": "^8.1",
"bref/bref": "^2.0.0",
"drupal/core": "^10",
"drupal/core-dev": "^10",
"mglaman/phpstan-drupal": "^1.2.0",
"nette/di": "^3",
"nette/neon": "^3.3",
"symfony/console": "^6.2"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
Loading

0 comments on commit 072ef31

Please sign in to comment.