Skip to content

Commit 072ef31

Browse files
authored
Add playground-runner (#628)
1 parent bde6445 commit 072ef31

14 files changed

+16156
-3
lines changed

.gitattributes

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ tests export-ignore
55
.travis.yml export-ignore
66
build.xml export-ignore
77
phpcs.xml export-ignore
8-
phpstan.neon export-ignore
8+
phpstan.neon export-ignore
9+
10+
/playground-runner export-ignore

.github/dependabot.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
- package-ecosystem: "composer"
8+
directory: "/playground-runner"
9+
schedule:
10+
interval: "weekly"
11+
ignore:
12+
- dependency-name: "drupal/core*"
13+
update-types: ["version-update:semver-major"]
14+
groups:
15+
drupal-core:
16+
patterns:
17+
- "drupal/core"
18+
- "drupal/core-dev"
19+
- package-ecosystem: "npm"
20+
directory: "/playground-runner"
21+
schedule:
22+
interval: "weekly"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: "Deploy Playground Runner"
2+
on:
3+
push:
4+
branches:
5+
- main
6+
paths:
7+
- '.github/workflows/deploy-playground-runner.yml'
8+
- 'playground-runner/**'
9+
concurrency: api_build
10+
jobs:
11+
deploy:
12+
name: "Build & deploy"
13+
runs-on: "ubuntu-latest"
14+
steps:
15+
- name: "Checkout"
16+
uses: actions/checkout@v3
17+
with:
18+
fetch-depth: 2
19+
- name: "Install Node"
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: "20"
23+
- name: "Install PHP"
24+
uses: "shivammathur/setup-php@v2"
25+
with:
26+
coverage: "none"
27+
php-version: "8.1"
28+
- name: "npm ci"
29+
working-directory: ./playground-runner
30+
run: "npm ci"
31+
- name: "composer install"
32+
working-directory: ./playground-runner
33+
run: "composer install --no-interaction --no-progress"
34+
- name: "phpunit"
35+
working-directory: ./playground-runner
36+
run: "php vendor/bin/phpunit"
37+
- name: "Deploy"
38+
working-directory: ./playground-runner
39+
env:
40+
AWS_DEFAULT_REGION: "us-east-1"
41+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
42+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
43+
run: "npm run deploy"

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
composer.phar
2-
composer.lock
1+
/composer.phar
2+
/composer.lock
33
/vendor/
44
/clover.xml
55
.circleci/config_local.yml
66

77
# Fix PHPUnit compatibility mutated class.
88
/tests/fixtures/TestCase.php
9+
/tests/fixtures/drupal/sites/simpletest/
910
.phpunit.result.cache
1011
.idea/

playground-runner/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor
2+
/.serverless
3+
/node_modules

playground-runner/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Test
2+
3+
```sh
4+
composer install
5+
npm ci
6+
serverless bref:local -f analyze --path test-event.json
7+
```

playground-runner/analyze.php

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
use PHPStan\AnalysedCodeException;
6+
use PHPStan\Analyser\Error;
7+
use PHPStan\Analyser\RuleErrorTransformer;
8+
use PHPStan\Analyser\ScopeContext;
9+
use PHPStan\Analyser\ScopeFactory;
10+
use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode;
11+
use PHPStan\BetterReflection\Reflection\Exception\CircularReference;
12+
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
13+
use PHPStan\Collectors\CollectedData;
14+
use PHPStan\Node\CollectedDataNode;
15+
use Symfony\Component\Console\Formatter\OutputFormatter;
16+
17+
require __DIR__.'/vendor/autoload.php';
18+
19+
error_reporting(E_ALL);
20+
ini_set('display_errors', '1');
21+
22+
$phpstanVersion = \Composer\InstalledVersions::getPrettyVersion('phpstan/phpstan');
23+
$phpstanDrupalVersion = \Composer\InstalledVersions::getPrettyVersion('mglaman/phpstan-drupal');
24+
$drupalCoreVersion = \Composer\InstalledVersions::getPrettyVersion('drupal/core');
25+
26+
/**
27+
* @param CollectedData[] $collectedData
28+
* @return Error[]
29+
*/
30+
function getCollectedDataErrors(\PHPStan\DependencyInjection\Container $container, array $collectedData): array
31+
{
32+
$nodeType = CollectedDataNode::class;
33+
$node = new CollectedDataNode($collectedData, true);
34+
$file = 'N/A';
35+
$scope = $container->getByType(ScopeFactory::class)->create(ScopeContext::create($file));
36+
$ruleRegistry = $container->getByType(\PHPStan\Rules\Registry::class);
37+
$ruleErrorTransformer = $container->getByType(RuleErrorTransformer::class);
38+
$errors = [];
39+
foreach ($ruleRegistry->getRules($nodeType) as $rule) {
40+
try {
41+
$ruleErrors = $rule->processNode($node, $scope);
42+
} catch (AnalysedCodeException $e) {
43+
$errors[] = new Error($e->getMessage(), $file, $node->getLine(), $e, null, null, $e->getTip());
44+
continue;
45+
} catch (IdentifierNotFound $e) {
46+
$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');
47+
continue;
48+
} catch (UnableToCompileNode | CircularReference $e) {
49+
$errors[] = new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, $node->getLine(), $e);
50+
continue;
51+
}
52+
53+
foreach ($ruleErrors as $ruleError) {
54+
$errors[] = $ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getLine());
55+
}
56+
}
57+
58+
return $errors;
59+
}
60+
61+
function clearTemp($tmpDir): void
62+
{
63+
$files = new RecursiveIteratorIterator(
64+
new RecursiveDirectoryIterator($tmpDir, RecursiveDirectoryIterator::SKIP_DOTS),
65+
RecursiveIteratorIterator::CHILD_FIRST
66+
);
67+
68+
foreach ($files as $fileinfo) {
69+
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
70+
$todo($fileinfo->getRealPath());
71+
}
72+
}
73+
74+
return function(array $event) use ($phpstanVersion, $phpstanDrupalVersion, $drupalCoreVersion) {
75+
$tmpDir = sys_get_temp_dir() . '/phpstan-runner';
76+
if (!is_dir($tmpDir)) {
77+
mkdir($tmpDir);
78+
}
79+
clearTemp($tmpDir);
80+
$code = $event['code'];
81+
$level = $event['level'];
82+
$codePath = sys_get_temp_dir() . '/phpstan-runner/tmp.php';
83+
file_put_contents($codePath, $code);
84+
85+
$rootDir = __DIR__;
86+
$configFiles = [
87+
$rootDir . '/playground.neon',
88+
$rootDir . '/vendor/mglaman/phpstan-drupal/extension.neon',
89+
$rootDir . '/vendor/mglaman/phpstan-drupal/rules.neon',
90+
$rootDir . '/vendor/phpstan/phpstan-deprecation-rules/rules.neon',
91+
];
92+
foreach ([
93+
'strictRules' => $rootDir . '/vendor/phpstan/phpstan-strict-rules/rules.neon',
94+
'bleedingEdge' => 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/conf/bleedingEdge.neon',
95+
] as $key => $file) {
96+
if (!isset($event[$key]) || !$event[$key]) {
97+
continue;
98+
}
99+
100+
$configFiles[] = $file;
101+
}
102+
$finalConfigFile = $tmpDir . '/run-phpstan-tmp.neon';
103+
$neon = \Nette\Neon\Neon::encode([
104+
'includes' => $configFiles,
105+
'parameters' => [
106+
'inferPrivatePropertyTypeFromConstructor' => true,
107+
'treatPhpDocTypesAsCertain' => $event['treatPhpDocTypesAsCertain'] ?? true,
108+
'phpVersion' => $event['phpVersion'] ?? 80000,
109+
'featureToggles' => [
110+
'disableRuntimeReflectionProvider' => true,
111+
],
112+
'drupal' => [
113+
'drupal_root' => \Composer\InstalledVersions::getInstallPath('drupal/core'),
114+
],
115+
],
116+
'services' => [
117+
'currentPhpVersionSimpleParser!' => [
118+
'factory' => '@currentPhpVersionRichParser',
119+
],
120+
],
121+
]);
122+
file_put_contents($finalConfigFile, $neon);
123+
124+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionUnionType.php';
125+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionIntersectionType.php';
126+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/ReflectionAttribute.php';
127+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Attribute.php';
128+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/UnitEnum.php';
129+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/BackedEnum.php';
130+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnum.php';
131+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnumUnitCase.php';
132+
require_once 'phar://' . $rootDir . '/vendor/phpstan/phpstan/phpstan.phar/stubs/runtime/Enum/ReflectionEnumBackedCase.php';
133+
134+
$containerFactory = new \PHPStan\DependencyInjection\ContainerFactory($tmpDir);
135+
$container = $containerFactory->create($tmpDir, [sprintf('%s/config.level%s.neon', $containerFactory->getConfigDirectory(), $level), $finalConfigFile], [$codePath]);
136+
137+
// Note: this is the big change from the parent script in phpstan-src.
138+
foreach ($container->getParameter('bootstrapFiles') as $bootstrapFileFromArray) {
139+
try {
140+
(static function (string $bootstrapFileFromArray) use ($container): void {
141+
require_once $bootstrapFileFromArray;
142+
})($bootstrapFileFromArray);
143+
} catch (Throwable $e) {
144+
$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());
145+
return ['result' => [$error], 'versions' => [
146+
'phpstan' => $phpstanVersion,
147+
'phpstan-drupal' => $phpstanDrupalVersion,
148+
'drupal' => $drupalCoreVersion,
149+
]];
150+
}
151+
}
152+
153+
/** @var \PHPStan\Analyser\Analyser $analyser */
154+
$analyser = $container->getByType(\PHPStan\Analyser\Analyser::class);
155+
$analyserResult = $analyser->analyse([$codePath], null, null, false, [$codePath]);
156+
$hasInternalErrors = count($analyserResult->getInternalErrors()) > 0 || $analyserResult->hasReachedInternalErrorsCountLimit();
157+
$results = $analyserResult->getErrors();
158+
159+
if (!$hasInternalErrors) {
160+
foreach (getCollectedDataErrors($container, $analyserResult->getCollectedData()) as $error) {
161+
$results[] = $error;
162+
}
163+
}
164+
165+
error_clear_last();
166+
167+
$errors = [];
168+
$tipFormatter = new OutputFormatter(false);
169+
foreach ($results as $result) {
170+
$error = [
171+
'message' => $result->getMessage(),
172+
'line' => $result->getLine(),
173+
'ignorable' => $result->canBeIgnored(),
174+
];
175+
if ($result->getTip() !== null) {
176+
$error['tip'] = $tipFormatter->format($result->getTip());
177+
}
178+
if ($result->getIdentifier() !== null) {
179+
$error['identifier'] = $result->getIdentifier();
180+
}
181+
$errors[] = $error;
182+
}
183+
184+
return ['result' => $errors, 'versions' => [
185+
'phpstan' => $phpstanVersion,
186+
'phpstan-drupal' => $phpstanDrupalVersion,
187+
'drupal' => $drupalCoreVersion,
188+
]];
189+
};

playground-runner/composer.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"require": {
3+
"php": "^8.1",
4+
"bref/bref": "^2.0.0",
5+
"drupal/core": "^10",
6+
"drupal/core-dev": "^10",
7+
"mglaman/phpstan-drupal": "^1.2.0",
8+
"nette/di": "^3",
9+
"nette/neon": "^3.3",
10+
"symfony/console": "^6.2"
11+
},
12+
"minimum-stability": "dev",
13+
"prefer-stable": true,
14+
"config": {
15+
"allow-plugins": {
16+
"phpstan/extension-installer": true,
17+
"dealerdirect/phpcodesniffer-composer-installer": true
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)