1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Testing;
4:
5: use PHPStan\Analyser\ConstantResolver;
6: use PHPStan\Analyser\DirectInternalScopeFactoryFactory;
7: use PHPStan\Analyser\Error;
8: use PHPStan\Analyser\ScopeFactory;
9: use PHPStan\Analyser\TypeSpecifier;
10: use PHPStan\BetterReflection\Reflector\Reflector;
11: use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
12: use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
13: use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
14: use PHPStan\Node\Printer\ExprPrinter;
15: use PHPStan\Parser\Parser;
16: use PHPStan\Php\ComposerPhpVersionFactory;
17: use PHPStan\Php\PhpVersion;
18: use PHPStan\PhpDoc\TypeNodeResolver;
19: use PHPStan\PhpDoc\TypeStringResolver;
20: use PHPStan\Reflection\AttributeReflectionFactory;
21: use PHPStan\Reflection\InitializerExprTypeResolver;
22: use PHPStan\Reflection\ReflectionProvider;
23: use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider;
24: use PHPStan\Rules\Properties\PropertyReflectionFinder;
25: use PHPStan\Type\Constant\OversizedArrayBuilder;
26: use PHPStan\Type\TypeAliasResolver;
27: use PHPStan\Type\UsefulTypeAliasResolver;
28: use PHPUnit\Framework\ExpectationFailedException;
29: use PHPUnit\Framework\TestCase;
30: use function count;
31: use function implode;
32: use function rtrim;
33: use function sprintf;
34: use const DIRECTORY_SEPARATOR;
35:
36: /** @api */
37: abstract class PHPStanTestCase extends TestCase
38: {
39:
40: use PHPStanTestCaseTrait;
41:
42: public static function getParser(): Parser
43: {
44: /** @var Parser $parser */
45: $parser = self::getContainer()->getService('defaultAnalysisParser');
46: return $parser;
47: }
48:
49: /** @api */
50: public static function createReflectionProvider(): ReflectionProvider
51: {
52: return self::getContainer()->getByType(ReflectionProvider::class);
53: }
54:
55: public static function getReflector(): Reflector
56: {
57: return self::getContainer()->getService('betterReflectionReflector');
58: }
59:
60: public static function getClassReflectionExtensionRegistryProvider(): ClassReflectionExtensionRegistryProvider
61: {
62: return self::getContainer()->getByType(ClassReflectionExtensionRegistryProvider::class);
63: }
64:
65: /**
66: * @param string[] $dynamicConstantNames
67: */
68: public static function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier, array $dynamicConstantNames = []): ScopeFactory
69: {
70: $container = self::getContainer();
71:
72: if (count($dynamicConstantNames) === 0) {
73: $dynamicConstantNames = $container->getParameter('dynamicConstantNames');
74: }
75:
76: $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider);
77: $composerPhpVersionFactory = $container->getByType(ComposerPhpVersionFactory::class);
78: $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, phpVersion: null, composerPhpVersionFactory: $composerPhpVersionFactory, container: $container);
79:
80: $initializerExprTypeResolver = new InitializerExprTypeResolver(
81: $constantResolver,
82: $reflectionProviderProvider,
83: $container->getByType(PhpVersion::class),
84: $container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class),
85: new OversizedArrayBuilder(),
86: $container->getParameter('usePathConstantsAsConstantString'),
87: );
88:
89: return new ScopeFactory(
90: new DirectInternalScopeFactoryFactory(
91: $container,
92: $reflectionProvider,
93: $initializerExprTypeResolver,
94: $container->getByType(ExpressionTypeResolverExtensionRegistryProvider::class),
95: $container->getByType(ExprPrinter::class),
96: $typeSpecifier,
97: new PropertyReflectionFinder(),
98: self::getParser(),
99: $container->getByType(PhpVersion::class),
100: $container->getByType(AttributeReflectionFactory::class),
101: $container->getParameter('phpVersion'),
102: $constantResolver,
103: ),
104: );
105: }
106:
107: /**
108: * @param array<string, string> $globalTypeAliases
109: */
110: public static function createTypeAliasResolver(array $globalTypeAliases, ReflectionProvider $reflectionProvider): TypeAliasResolver
111: {
112: $container = self::getContainer();
113:
114: return new UsefulTypeAliasResolver(
115: $globalTypeAliases,
116: $container->getByType(TypeStringResolver::class),
117: $container->getByType(TypeNodeResolver::class),
118: $reflectionProvider,
119: );
120: }
121:
122: protected function shouldTreatPhpDocTypesAsCertain(): bool
123: {
124: return true;
125: }
126:
127: /**
128: * Provides a DIRECTORY_SEPARATOR agnostic assertion helper, to compare file paths.
129: *
130: */
131: protected function assertSamePaths(string $expected, string $actual, string $message = ''): void
132: {
133: $expected = $this->getFileHelper()->normalizePath($expected);
134: $actual = $this->getFileHelper()->normalizePath($actual);
135:
136: $this->assertSame($expected, $actual, $message);
137: }
138:
139: /**
140: * @param Error[]|string[] $errors
141: */
142: protected function assertNoErrors(array $errors): void
143: {
144: try {
145: $this->assertCount(0, $errors);
146: } catch (ExpectationFailedException $e) {
147: $messages = [];
148: foreach ($errors as $error) {
149: if ($error instanceof Error) {
150: $messages[] = sprintf("- %s\n in %s on line %d\n", rtrim($error->getMessage(), '.'), $error->getFile(), $error->getLine() ?? 0);
151: } else {
152: $messages[] = $error;
153: }
154: }
155:
156: $this->fail($e->getMessage() . "\n\nEmitted errors:\n" . implode("\n", $messages));
157: }
158: }
159:
160: protected function skipIfNotOnWindows(): void
161: {
162: if (DIRECTORY_SEPARATOR === '\\') {
163: return;
164: }
165:
166: self::markTestSkipped();
167: }
168:
169: protected function skipIfNotOnUnix(): void
170: {
171: if (DIRECTORY_SEPARATOR === '/') {
172: return;
173: }
174:
175: self::markTestSkipped();
176: }
177:
178: }
179: