diff --git a/bin/typephp b/bin/typephp index 33b63c8..4f8f044 100644 --- a/bin/typephp +++ b/bin/typephp @@ -15,7 +15,7 @@ foreach ($autoloadFiles as $file) { } } -use TypePHP\Command\CommandRunner; +use TypePHP\Internal\Cli\CommandRunner; $exitCode = CommandRunner::run(array_slice($argv, 1)); exit($exitCode); \ No newline at end of file diff --git a/composer.json b/composer.json index 9f94162..02a4dfe 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ ], "require": { "php": "^8.1", - "phpstan/phpdoc-parser": "^2.0", + "phpstan/phpdoc-parser": "^2.3", "nikic/php-parser": "^5.3" }, "require-dev": { diff --git a/src/Internal/ContractVisitor.php b/src/Internal/Ast/ContractVisitor.php similarity index 94% rename from src/Internal/ContractVisitor.php rename to src/Internal/Ast/ContractVisitor.php index fc03ad4..eec3028 100644 --- a/src/Internal/ContractVisitor.php +++ b/src/Internal/Ast/ContractVisitor.php @@ -2,15 +2,11 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Ast; use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -use TypePHP\Contract\DocblockExtractor; -use TypePHP\Internal\Visitor\FunctionContractInjector; -use TypePHP\Internal\Visitor\NodeBuilder; -use TypePHP\Internal\Visitor\PropertyHookInjector; -use TypePHP\Internal\Visitor\ScopeManager; +use TypePHP\Internal\Docblock\DocblockExtractor; /** * @internal AST Node Visitor that injects contract checks, scope tracking, property hook validation, and parameter/return wrappers into functions and methods. @@ -31,7 +27,8 @@ public function __construct() */ public function enterNode(Node $node): ?array { - if ($node instanceof Node\Stmt\Function_ + if ( + $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction @@ -165,12 +162,12 @@ public function leaveNode(Node $node): Node|null $node->setAttribute('typephp_wrapped', value: true); return new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::cloneInstance'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::cloneInstance'), [ new Node\Arg( new Node\Expr\Clone_( new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::prepareClone'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::prepareClone'), [new Node\Arg($node->expr)] ) ) @@ -180,7 +177,8 @@ public function leaveNode(Node $node): Node|null ); } - if ($node instanceof Node\Stmt\Function_ + if ( + $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction diff --git a/src/Internal/Visitor/FunctionContractInjector.php b/src/Internal/Ast/FunctionContractInjector.php similarity index 92% rename from src/Internal/Visitor/FunctionContractInjector.php rename to src/Internal/Ast/FunctionContractInjector.php index c732621..a26161a 100644 --- a/src/Internal/Visitor/FunctionContractInjector.php +++ b/src/Internal/Ast/FunctionContractInjector.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal\Visitor; +namespace TypePHP\Internal\Ast; use PhpParser\Node; use PhpParser\NodeTraverser; @@ -204,7 +204,7 @@ private static function buildSetupScopeStmt(array $params, Node\Expr $thisArg): ); $checkCall = new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::setupScope'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::setupScope'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg($argsExpr), @@ -217,7 +217,7 @@ private static function buildSetupScopeStmt(array $params, Node\Expr $thisArg): $ifStmt = new Node\Stmt\If_( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpErr'), $checkCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), ['stmts' => [$throwStmt]] ); @@ -242,7 +242,7 @@ private static function buildCallableParamWrappers(array $params, string $docTex new Node\Expr\Assign( new Node\Expr\Variable($paramName), new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::wrapCallable'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::wrapCallable'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg(new Node\Scalar\String_($paramName)), @@ -275,7 +275,7 @@ private static function buildIterableParamWrappers(array $params, string $docTex new Node\Expr\Assign( new Node\Expr\Variable($paramName), new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::wrapIterable'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::wrapIterable'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg(new Node\Scalar\String_($paramName)), @@ -373,12 +373,12 @@ public static function buildTypeErrorThrowStmt(Node\Expr $errorVar): Node\Stmt\E return new Node\Stmt\Expression( new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall($errorVar, 'getMessage') @@ -399,7 +399,7 @@ public static function buildReturnCheckCall(Node\Expr $exprToWrap, Node\Expr $th : new Node\Expr\Array_(); return new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::checkReturn'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::checkReturn'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg($exprToWrap), @@ -417,7 +417,7 @@ public static function buildVoidReturnGuard(Node\Expr\FuncCall $checkCall): arra $ifStmt = new Node\Stmt\If_( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpRet'), $checkCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), ['stmts' => [self::buildTypeErrorThrowStmt(new Node\Expr\Variable('__typephpRet'))]] ); @@ -434,16 +434,16 @@ public static function buildTernaryReturnExpr(Node\Expr\FuncCall $checkCall): No return new Node\Expr\Ternary( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpRet'), $checkCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall(new Node\Expr\Variable('__typephpRet'), 'getMessage') @@ -461,7 +461,7 @@ public static function buildTernaryReturnExpr(Node\Expr\FuncCall $checkCall): No public static function buildWrappedYieldNode(Node\Expr\Yield_ $n, Node\Expr $thisArg): Node\Expr\Ternary { $checkYieldCall = new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::checkYield'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::checkYield'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg($n->key ?? new Node\Expr\ConstFetch(new Node\Name('null'))), @@ -473,16 +473,16 @@ public static function buildWrappedYieldNode(Node\Expr\Yield_ $n, Node\Expr $thi $n->value = new Node\Expr\Ternary( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpYld'), $checkYieldCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall(new Node\Expr\Variable('__typephpYld'), 'getMessage') @@ -498,7 +498,7 @@ public static function buildWrappedYieldNode(Node\Expr\Yield_ $n, Node\Expr $thi ); $checkSendCall = new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::checkSend'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::checkSend'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg($n), @@ -509,16 +509,16 @@ public static function buildWrappedYieldNode(Node\Expr\Yield_ $n, Node\Expr $thi return new Node\Expr\Ternary( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpSnd'), $checkSendCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall(new Node\Expr\Variable('__typephpSnd'), 'getMessage') @@ -570,7 +570,7 @@ public function enterNode(Node $n): int|Node|null $n->setAttribute('typephp_wrapped', true); $n->expr = new Node\Expr\FuncCall( - new Node\Name('\TypePHP\Internal\RuntimeTypeChecker::wrapIterable'), + new Node\Name\FullyQualified('TypePHP\Internal\RuntimeTypeChecker::wrapIterable'), [ new Node\Arg(new Node\Scalar\MagicConst\Method()), new Node\Arg(new Node\Scalar\String_('return')), @@ -649,4 +649,4 @@ public function enterNode(Node $n): int|array|null return $newStmts; } -} \ No newline at end of file +} diff --git a/src/Internal/Visitor/NodeBuilder.php b/src/Internal/Ast/NodeBuilder.php similarity index 89% rename from src/Internal/Visitor/NodeBuilder.php rename to src/Internal/Ast/NodeBuilder.php index cdd4a98..0fdde1a 100644 --- a/src/Internal/Visitor/NodeBuilder.php +++ b/src/Internal/Ast/NodeBuilder.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal\Visitor; +namespace TypePHP\Internal\Ast; use PhpParser\Node; @@ -42,7 +42,7 @@ public static function createTernaryThrowExpr(Node\Expr\FuncCall $checkCall, int $args = [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall( @@ -65,11 +65,11 @@ public static function createTernaryThrowExpr(Node\Expr\FuncCall $checkCall, int new Node\Expr\Variable('__typephpVal'), $checkCall ), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', $args ) diff --git a/src/Internal/Visitor/PropertyHookInjector.php b/src/Internal/Ast/PropertyHookInjector.php similarity index 93% rename from src/Internal/Visitor/PropertyHookInjector.php rename to src/Internal/Ast/PropertyHookInjector.php index 15cb940..93f6278 100644 --- a/src/Internal/Visitor/PropertyHookInjector.php +++ b/src/Internal/Ast/PropertyHookInjector.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace TypePHP\Internal\Visitor; +namespace TypePHP\Internal\Ast; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; /** * @internal Injects property contract checks into PHP 8.4 get and set property hooks. @@ -90,16 +90,16 @@ private static function buildExpressionSetHookTernary(Node\Expr\FuncCall $checkC return new Node\Expr\Ternary( new Node\Expr\Instanceof_( new Node\Expr\Assign(new Node\Expr\Variable('__typephpVal'), $checkCall), - new Node\Name('\TypePHP\Internal\ErrorMessage') + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorMessage') ), new Node\Expr\Throw_( new Node\Expr\StaticCall( - new Node\Name('\TypePHP\Internal\ErrorFactory'), + new Node\Name\FullyQualified('TypePHP\Internal\Diagnostic\ErrorFactory'), 'prepareException', [ new Node\Arg( new Node\Expr\New_( - new Node\Name('\TypePHP\Exception\TypeError'), + new Node\Name\FullyQualified('TypePHP\Exception\TypeError'), [ new Node\Arg( new Node\Expr\MethodCall(new Node\Expr\Variable('__typephpVal'), 'getMessage') diff --git a/src/Internal/ScopeCleaner.php b/src/Internal/Ast/ScopeCleaner.php similarity index 79% rename from src/Internal/ScopeCleaner.php rename to src/Internal/Ast/ScopeCleaner.php index 18a22ba..a85e442 100644 --- a/src/Internal/ScopeCleaner.php +++ b/src/Internal/Ast/ScopeCleaner.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Ast; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Generics\TemplateManager; /** * @internal ensures proper scope cleanup for variable tracking. diff --git a/src/Internal/Visitor/ScopeManager.php b/src/Internal/Ast/ScopeManager.php similarity index 95% rename from src/Internal/Visitor/ScopeManager.php rename to src/Internal/Ast/ScopeManager.php index d6b3187..ea3ea2b 100644 --- a/src/Internal/Visitor/ScopeManager.php +++ b/src/Internal/Ast/ScopeManager.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace TypePHP\Internal\Visitor; +namespace TypePHP\Internal\Ast; use PhpParser\Node; use PHPStan\PhpDocParser\Parser\TokenIterator; -use TypePHP\Contract\DocblockExtractor; -use TypePHP\Internal\DocblockNormalizer; +use TypePHP\Internal\Docblock\DocblockExtractor; +use TypePHP\Internal\Docblock\DocblockNormalizer; /** * @internal Manages lexical scope stack frames and extracts local @var variable annotations. diff --git a/src/Internal/TypePHPPrinter.php b/src/Internal/Ast/TypePHPPrinter.php similarity index 97% rename from src/Internal/TypePHPPrinter.php rename to src/Internal/Ast/TypePHPPrinter.php index e2b9341..aefdd5b 100644 --- a/src/Internal/TypePHPPrinter.php +++ b/src/Internal/Ast/TypePHPPrinter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Ast; use PhpParser\Node; use PhpParser\PrettyPrinter\Standard; diff --git a/src/Internal/Checker/GeneratorChecker.php b/src/Internal/Checker/GeneratorChecker.php index 7516377..74ef9fd 100644 --- a/src/Internal/Checker/GeneratorChecker.php +++ b/src/Internal/Checker/GeneratorChecker.php @@ -8,11 +8,11 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Contract\ContractParser; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * @internal Evaluates generator yield and send (TSend) type validations. @@ -86,7 +86,7 @@ public static function checkYield( */ private static function resolveGeneratorReturnType(string $function, object|string|null $thisOrClass): ?TypeNode { - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $returnTypeNode = $contract['return'] ?? null; if ($returnTypeNode === null) { diff --git a/src/Internal/Checker/InlineChecker.php b/src/Internal/Checker/InlineChecker.php index d8361ae..9f275d9 100644 --- a/src/Internal/Checker/InlineChecker.php +++ b/src/Internal/Checker/InlineChecker.php @@ -24,14 +24,14 @@ use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\ParserConfig; -use TypePHP\Contract\ContractParser; -use TypePHP\Internal\Config; -use TypePHP\Internal\DocblockNormalizer; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; -use TypePHP\Wrapper\CallableWrapper; +use TypePHP\Internal\Docblock\DocblockNormalizer; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Wrapper\CallableWrapper; /** * Evaluates inline variable (@var) and class property validation rules. @@ -213,7 +213,7 @@ public static function checkProperty(mixed $value, mixed $objectOrClass, string $className = \is_string($objectOrClass) ? $objectOrClass : \get_class($objectOrClass); - $typeNode = ContractParser::parseProperty($className, $propName); + $typeNode = DocblockParser::parseProperty($className, $propName); if ($typeNode === null) { return $value; } @@ -377,7 +377,7 @@ private static function resolveFunctionContext(TypeNode $typeNode, string $funct $refFunc = new \ReflectionFunction($functionName); $typeNode = SpecialTypeResolver::resolve($typeNode, $refFunc); - $contract = ContractParser::parse($functionName); + $contract = DocblockParser::parse($functionName); $declaredTemplates = $contract['templates'] ?? []; $aliases = $contract['aliases'] ?? []; $boundTemplates = TemplateManager::getBoundTemplates($functionName, null, $declaredTemplates); @@ -413,13 +413,13 @@ private static function resolveClassContext( $refClass = new \ReflectionClass($className); $typeNode = SpecialTypeResolver::resolve($typeNode, $refClass); - $classAliases = ContractParser::parseClassAliases($className); + $classAliases = DocblockParser::parseClassAliases($className); $targetFunc = ($methodName !== '{closure}' && $methodName !== null) ? $className . '::' . $methodName : $className . '::__construct'; - $contract = ContractParser::parse($targetFunc); + $contract = DocblockParser::parse($targetFunc); $declaredTemplates = $contract['allTemplates'] ?? ($contract['classTemplates'] ?? []); $boundTemplates = TemplateManager::getBoundTemplates($targetFunc, $thisObj, $declaredTemplates); @@ -442,7 +442,7 @@ private static function resolveClassContext( private static function substitutePropertyGenerics(TypeNode $typeNode, object $object, string $className): TypeNode { $constructorTarget = $className . '::__construct'; - $contract = ContractParser::parse($constructorTarget); + $contract = DocblockParser::parse($constructorTarget); $allTemplates = [...($contract['classTemplates'] ?? []), ...($contract['templates'] ?? [])]; $boundTemplates = TemplateManager::getBoundTemplates('none', $object, $allTemplates); diff --git a/src/Internal/Checker/ParamChecker.php b/src/Internal/Checker/ParamChecker.php index 7f30db1..e165e00 100644 --- a/src/Internal/Checker/ParamChecker.php +++ b/src/Internal/Checker/ParamChecker.php @@ -13,17 +13,17 @@ use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Contract\ContractParser; -use TypePHP\Contract\HierarchyResolver; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\HierarchyResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\ClassNameValidator; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * @internal Evaluates function and method parameter contract validations (including dynamic @method calls via __call / __callStatic). @@ -62,12 +62,17 @@ public static function checkParams( $effectiveFunction = self::resolveEffectiveFunction($function, $thisOrClass, $thisObj); } + // Fast-path: If no arguments were passed and it's not a magic __call dispatch, exit immediately + if ($vars === [] && ! str_ends_with($effectiveFunction, '::__call') && ! str_ends_with($effectiveFunction, '::__callStatic')) { + return null; + } + $magicError = self::handleMagicCall($effectiveFunction, $vars, $thisObj, $registry); if ($magicError !== null) { return $magicError; } - $contract = ContractParser::parse($effectiveFunction); + $contract = DocblockParser::parse($effectiveFunction); if (! $contract['hasParamContract']) { return null; @@ -218,7 +223,7 @@ private static function handleMagicCall( } $className = explode('::', $effectiveFunction, 2)[0]; - $magicContract = ContractParser::parseMagicMethod($className, $magicMethodName); + $magicContract = DocblockParser::parseMagicMethod($className, $magicMethodName); if ($magicContract === null) { return null; @@ -266,7 +271,7 @@ private static function inferTemplatesFromClosures( return; } - $contract = ContractParser::parse($effectiveFunction); + $contract = DocblockParser::parse($effectiveFunction); $classTemplates = $contract['classTemplates'] ?? []; foreach ($callableNodes as $cParamName => $cTypeNode) { @@ -509,7 +514,7 @@ private static function bindTemplateIfUnbound( ?object $thisObj, array $templates ): void { - $contract = ContractParser::parse($effectiveFunction); + $contract = DocblockParser::parse($effectiveFunction); $classTemplates = $contract['classTemplates'] ?? []; $isClassLevelTemplate = isset($classTemplates[$templateName]); $targetObj = $isClassLevelTemplate ? $thisObj : null; @@ -700,7 +705,7 @@ private static function resolveClassStringTemplate( $templateName = $innerType->name; $templateNode = $templates[$templateName]; - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $classTemplates = $contract['classTemplates'] ?? []; $isClassLevelTemplate = isset($classTemplates[$templateName]); $targetObj = $isClassLevelTemplate ? $thisObj : null; @@ -824,7 +829,7 @@ private static function resolveTemplateParam( $isVariadic = $typeNode instanceof ArrayTypeNode; $isNullable = ($typeNode instanceof NullableTypeNode) || ($typeNode instanceof UnionTypeNode && self::typeContainsNull($typeNode)); - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $classTemplates = $contract['classTemplates'] ?? []; $isClassLevelTemplate = isset($classTemplates[$templateName]); $targetObj = $isClassLevelTemplate ? $thisObj : null; diff --git a/src/Internal/Checker/ReturnChecker.php b/src/Internal/Checker/ReturnChecker.php index cd96abf..506e756 100644 --- a/src/Internal/Checker/ReturnChecker.php +++ b/src/Internal/Checker/ReturnChecker.php @@ -13,15 +13,14 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use ReflectionClass; use Traversable; -use TypePHP\Contract\ContractParser; -use TypePHP\Contract\HierarchyResolver; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\Config; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; -use TypePHP\Wrapper\CallableWrapper; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\HierarchyResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Wrapper\CallableWrapper; /** * @internal Evaluates function and method return contract validations (including dynamic @method calls via __call / __callStatic). @@ -72,7 +71,7 @@ public static function checkReturn( return $magicResult; } - $contract = ContractParser::parse($effectiveFunction); + $contract = DocblockParser::parse($effectiveFunction); if (! ($contract['hasReturnContract'] ?? ($contract['return'] !== null))) { return $value; @@ -186,7 +185,7 @@ private static function handleMagicReturn( } $className = explode('::', $effectiveFunction, 2)[0]; - $magicContract = ContractParser::parseMagicMethod($className, $magicMethodName); + $magicContract = DocblockParser::parseMagicMethod($className, $magicMethodName); if ($magicContract === null || $magicContract['return'] === null) { return null; diff --git a/src/Command/CacheClearCommand.php b/src/Internal/Cli/CacheClearCommand.php similarity index 89% rename from src/Command/CacheClearCommand.php rename to src/Internal/Cli/CacheClearCommand.php index a92671a..556dd83 100644 --- a/src/Command/CacheClearCommand.php +++ b/src/Internal/Cli/CacheClearCommand.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; -use TypePHP\Internal\CacheManager; +use TypePHP\Internal\Io\CacheManager; /** * @Command Clears the TypePHP cache. diff --git a/src/Command/CacheRebuildCommand.php b/src/Internal/Cli/CacheRebuildCommand.php similarity index 93% rename from src/Command/CacheRebuildCommand.php rename to src/Internal/Cli/CacheRebuildCommand.php index 3441fcf..3a6373b 100644 --- a/src/Command/CacheRebuildCommand.php +++ b/src/Internal/Cli/CacheRebuildCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; /** * @internal Rebuilds the TypePHP cache. diff --git a/src/Command/CacheWarmCommand.php b/src/Internal/Cli/CacheWarmCommand.php similarity index 94% rename from src/Command/CacheWarmCommand.php rename to src/Internal/Cli/CacheWarmCommand.php index 5f924ef..d73a698 100644 --- a/src/Command/CacheWarmCommand.php +++ b/src/Internal/Cli/CacheWarmCommand.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; -use TypePHP\Internal\CacheManager; +use TypePHP\Internal\Io\CacheManager; /** * @internal Warms up the TypePHP cache. diff --git a/src/Command/CliFormatter.php b/src/Internal/Cli/CliFormatter.php similarity index 98% rename from src/Command/CliFormatter.php rename to src/Internal/Cli/CliFormatter.php index df4449d..6aeb361 100644 --- a/src/Command/CliFormatter.php +++ b/src/Internal/Cli/CliFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; /** * @internal Helper class for ANSI color formatting and terminal STDOUT/STDERR output styling. diff --git a/src/Command/CommandInterface.php b/src/Internal/Cli/CommandInterface.php similarity index 93% rename from src/Command/CommandInterface.php rename to src/Internal/Cli/CommandInterface.php index f904e32..efc19d5 100644 --- a/src/Command/CommandInterface.php +++ b/src/Internal/Cli/CommandInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; /** * @internal Contract defining a CLI command action. diff --git a/src/Command/CommandRunner.php b/src/Internal/Cli/CommandRunner.php similarity index 98% rename from src/Command/CommandRunner.php rename to src/Internal/Cli/CommandRunner.php index 0d17e83..939d660 100644 --- a/src/Command/CommandRunner.php +++ b/src/Internal/Cli/CommandRunner.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; final class CommandRunner { diff --git a/src/Command/ConfigInitCommand.php b/src/Internal/Cli/ConfigInitCommand.php similarity index 99% rename from src/Command/ConfigInitCommand.php rename to src/Internal/Cli/ConfigInitCommand.php index 2a7d5d3..af9fdd0 100644 --- a/src/Command/ConfigInitCommand.php +++ b/src/Internal/Cli/ConfigInitCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; final class ConfigInitCommand implements CommandInterface { diff --git a/src/Command/HelpCommand.php b/src/Internal/Cli/HelpCommand.php similarity index 97% rename from src/Command/HelpCommand.php rename to src/Internal/Cli/HelpCommand.php index 56d466e..a82d39b 100644 --- a/src/Command/HelpCommand.php +++ b/src/Internal/Cli/HelpCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; final class HelpCommand implements CommandInterface { diff --git a/src/Command/RunCommand.php b/src/Internal/Cli/RunCommand.php similarity index 98% rename from src/Command/RunCommand.php rename to src/Internal/Cli/RunCommand.php index ffb3b55..a6050e1 100644 --- a/src/Command/RunCommand.php +++ b/src/Internal/Cli/RunCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Command; +namespace TypePHP\Internal\Cli; use TypePHP\TypePHP; diff --git a/src/Internal/ErrorFactory.php b/src/Internal/Diagnostic/ErrorFactory.php similarity index 96% rename from src/Internal/ErrorFactory.php rename to src/Internal/Diagnostic/ErrorFactory.php index 3ba4eb3..3898544 100644 --- a/src/Internal/ErrorFactory.php +++ b/src/Internal/Diagnostic/ErrorFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Diagnostic; use ReflectionClass; use Throwable; @@ -15,11 +15,6 @@ final class ErrorFactory { private const INTERNAL_DIR_PATTERNS = [ 'src/Internal/', - 'src/Wrapper/', - 'src/Validator/', - 'src/Resolver/', - 'src/Contract/', - 'src/Command/', 'bin/typephp', ]; @@ -157,7 +152,7 @@ private static function sanitizeMessage(string $message, ?string $targetFile, ?i $message = $cleaned; } - return str_replace('TypePHP\Command\RunCommand::', '', $message); + return str_replace('TypePHP\Internal\Cli\RunCommand::', '', $message); } /** diff --git a/src/Internal/ErrorMessage.php b/src/Internal/Diagnostic/ErrorMessage.php similarity index 89% rename from src/Internal/ErrorMessage.php rename to src/Internal/Diagnostic/ErrorMessage.php index 26ffd83..f24f022 100644 --- a/src/Internal/ErrorMessage.php +++ b/src/Internal/Diagnostic/ErrorMessage.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Diagnostic; /** * @internal Value object holding validation error messages prior to call-site exception throwing. diff --git a/src/Internal/TypeFormatter.php b/src/Internal/Diagnostic/TypeFormatter.php similarity index 98% rename from src/Internal/TypeFormatter.php rename to src/Internal/Diagnostic/TypeFormatter.php index 4a2b22a..c536ffa 100644 --- a/src/Internal/TypeFormatter.php +++ b/src/Internal/Diagnostic/TypeFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Diagnostic; /** * @internal diff --git a/src/Contract/DocblockExtractor.php b/src/Internal/Docblock/DocblockExtractor.php similarity index 91% rename from src/Contract/DocblockExtractor.php rename to src/Internal/Docblock/DocblockExtractor.php index 5517ba7..9f76759 100644 --- a/src/Contract/DocblockExtractor.php +++ b/src/Internal/Docblock/DocblockExtractor.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Contract; +namespace TypePHP\Internal\Docblock; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; @@ -17,10 +17,9 @@ use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\ParserConfig; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\DocblockNormalizer; -use TypePHP\Internal\StubManager; -use TypePHP\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\ClassNameValidator; +use TypePHP\Internal\Util\StubManager; /** * @internal Encapsulates PHPDoc AST parsing, tokenizing, and tag extractions. @@ -34,6 +33,12 @@ final class DocblockExtractor */ private static array $docParseCache = []; + private static ?PhpDocParser $phpDocParser = null; + + private static ?TypeParser $typeParser = null; + + private static ?Lexer $lexer = null; + /** * Resets the parsed docblock cache. Useful for test isolation. */ @@ -49,20 +54,42 @@ public static function reset(): void */ public static function getParserComponents(): array { - /** @var PhpDocParser|null $phpDocParser */ - static $phpDocParser = null; - /** @var Lexer|null $lexer */ - static $lexer = null; + self::initParserComponents(); + + /** @var PhpDocParser $docParser */ + $docParser = self::$phpDocParser; + /** @var Lexer $lexer */ + $lexer = self::$lexer; + + return [$docParser, $lexer]; + } + + /** + * Returns shared static instances of PHPStan's TypeParser and Lexer. + * + * @return array{TypeParser, Lexer} + */ + public static function getTypeParserComponents(): array + { + self::initParserComponents(); + + /** @var TypeParser $typeParser */ + $typeParser = self::$typeParser; + /** @var Lexer $lexer */ + $lexer = self::$lexer; - if ($phpDocParser === null || $lexer === null) { + return [$typeParser, $lexer]; + } + + private static function initParserComponents(): void + { + if (self::$phpDocParser === null || self::$typeParser === null || self::$lexer === null) { $config = new ParserConfig(usedAttributes: []); - $lexer = new Lexer($config); + self::$lexer = new Lexer($config); $constExprParser = new ConstExprParser($config); - $typeParser = new TypeParser($config, $constExprParser); - $phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); + self::$typeParser = new TypeParser($config, $constExprParser); + self::$phpDocParser = new PhpDocParser($config, self::$typeParser, $constExprParser); } - - return [$phpDocParser, $lexer]; } /** @@ -358,7 +385,7 @@ public static function extractAliases( } foreach ($aliases as $name => $type) { - $aliases[$name] = ContractParser::substituteAliases($type, $aliases); + $aliases[$name] = DocblockParser::substituteAliases($type, $aliases); } } diff --git a/src/Internal/DocblockNormalizer.php b/src/Internal/Docblock/DocblockNormalizer.php similarity index 94% rename from src/Internal/DocblockNormalizer.php rename to src/Internal/Docblock/DocblockNormalizer.php index e293ea1..f89893d 100644 --- a/src/Internal/DocblockNormalizer.php +++ b/src/Internal/Docblock/DocblockNormalizer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Docblock; /** * Normalizes PHPDoc comment strings before AST parsing. @@ -29,6 +29,10 @@ final class DocblockNormalizer */ public static function normalize(string $doc): string { + if (! str_contains($doc, '@')) { + return $doc; + } + if (str_contains($doc, '-type') && str_contains($doc, '=')) { $doc = preg_replace('/(@(?:phpstan|psalm)-type\s+[a-zA-Z0-9_\x80-\xff]+)\s*=\s*/', '$1 ', $doc) ?? $doc; } diff --git a/src/Contract/ContractParser.php b/src/Internal/Docblock/DocblockParser.php similarity index 99% rename from src/Contract/ContractParser.php rename to src/Internal/Docblock/DocblockParser.php index 7aeb20d..34e74d2 100644 --- a/src/Contract/ContractParser.php +++ b/src/Internal/Docblock/DocblockParser.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Contract; +namespace TypePHP\Internal\Docblock; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; @@ -22,15 +22,17 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use TypePHP\Internal\Checker\InlineChecker; -use TypePHP\Internal\Config; -use TypePHP\Internal\StubManager; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Resolver\HierarchyResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; +use TypePHP\Internal\Util\StubManager; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * @internal Main orchestrator parsing and caching PHPDoc contracts (@param, @return, @template, @phpstan-type, @var, stubs). */ -final class ContractParser +final class DocblockParser { /** * Cache for resolved contract metadata. diff --git a/src/Resolver/TemplateManager.php b/src/Internal/Generics/TemplateManager.php similarity index 96% rename from src/Resolver/TemplateManager.php rename to src/Internal/Generics/TemplateManager.php index c9254c0..d832410 100644 --- a/src/Resolver/TemplateManager.php +++ b/src/Internal/Generics/TemplateManager.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Resolver; +namespace TypePHP\Internal\Generics; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; @@ -13,19 +13,16 @@ use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use PHPStan\PhpDocParser\Lexer\Lexer; -use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\TokenIterator; -use PHPStan\PhpDocParser\Parser\TypeParser; -use PHPStan\PhpDocParser\ParserConfig; -use TypePHP\Contract\ContractParser; -use TypePHP\Contract\DocblockExtractor; -use TypePHP\Contract\FileFilter; -use TypePHP\Contract\HierarchyResolver; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\StubManager; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Docblock\DocblockExtractor; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Resolver\HierarchyResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\ClassNameValidator; +use TypePHP\Internal\Util\FileFilter; +use TypePHP\Internal\Util\StubManager; use WeakMap; /** @@ -63,6 +60,13 @@ final class TemplateManager */ private static array $classInheritedBindingsCache = []; + /** + * In-memory cache for subclass hierarchy (is_a) lookups. + * + * @var array + */ + private static array $subclassCache = []; + /** * Temporary storage for an original object instance being cloned. */ @@ -309,6 +313,7 @@ public static function reset(): void self::$callStackBindings = []; self::$classHierarchyTemplatesCache = []; self::$classInheritedBindingsCache = []; + self::$subclassCache = []; self::$pendingCloneSource = null; } @@ -408,7 +413,7 @@ public static function getBoundTemplates(string $function, ?object $thisObj, arr $topFrame = end(self::$callStackBindings[$function]); if ($topFrame !== false) { if ($thisObj !== null) { - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $methodTemplates = $contract['templates'] ?? []; foreach ($topFrame as $tName => $tNode) { if (isset($methodTemplates[$tName])) { @@ -443,7 +448,7 @@ public static function getBoundTemplatesForInstance(object $instance): array } /** - * Retrieves all declared template variances ('covariant', 'contravariant', 'invariant') for an object instance. + * Retrieves all declared template variances ('covariant', 'contravariant', or 'invariant') for an object instance. * * @return array */ @@ -1121,22 +1126,29 @@ private static function checkNestedGenericVariance(GenericTypeNode $existing, Ge private static function isSubclass(string $sub, string $super): bool { + $cacheKey = $sub . '|' . $super; + if (isset(self::$subclassCache[$cacheKey])) { + return self::$subclassCache[$cacheKey]; + } + $baseSub = ($pos = strpos($sub, '<')) !== false ? substr($sub, 0, $pos) : $sub; $baseSuper = ($pos = strpos($super, '<')) !== false ? substr($super, 0, $pos) : $super; $baseSub = ltrim(trim($baseSub), '\\'); $baseSuper = ltrim(trim($baseSuper), '\\'); + $result = false; + if ( ClassNameValidator::isValid($baseSub) && ClassNameValidator::isValid($baseSuper) && (class_exists($baseSub) || interface_exists($baseSub) || trait_exists($baseSub) || enum_exists($baseSub)) && (class_exists($baseSuper) || interface_exists($baseSuper) || trait_exists($baseSuper) || enum_exists($baseSuper)) ) { - return is_a($baseSub, $baseSuper, true); + $result = is_a($baseSub, $baseSuper, true); } - return false; + return self::$subclassCache[$cacheKey] = $result; } /** @@ -1145,7 +1157,7 @@ private static function isSubclass(string $sub, string $super): bool public static function bindInstance(object $instance, string $typeString, string $file = ''): object { try { - [$typeParser, $lexer] = self::getTypeParserComponents(); + [$typeParser, $lexer] = DocblockExtractor::getTypeParserComponents(); $tokens = new TokenIterator($lexer->tokenize($typeString)); $typeNode = $typeParser->parse($tokens); @@ -1223,7 +1235,7 @@ private static function resolveTypeNodeAst(TypeNode $n, \ReflectionClass $ref): } if ($n instanceof GenericTypeNode) { $base = new IdentifierTypeNode(SpecialTypeResolver::resolveFqcn($n->type->name, $ref)); - $generics = array_map(fn($t) => self::resolveTypeNodeAst($t, $ref), $n->genericTypes); + $generics = array_map(fn ($t) => self::resolveTypeNodeAst($t, $ref), $n->genericTypes); return new GenericTypeNode($base, $generics, $n->variances); } @@ -1234,34 +1246,12 @@ private static function resolveTypeNodeAst(TypeNode $n, \ReflectionClass $ref): return new NullableTypeNode(self::resolveTypeNodeAst($n->type, $ref)); } if ($n instanceof UnionTypeNode) { - return new UnionTypeNode(array_map(fn($t) => self::resolveTypeNodeAst($t, $ref), $n->types)); + return new UnionTypeNode(array_map(fn ($t) => self::resolveTypeNodeAst($t, $ref), $n->types)); } if ($n instanceof IntersectionTypeNode) { - return new IntersectionTypeNode(array_map(fn($t) => self::resolveTypeNodeAst($t, $ref), $n->types)); + return new IntersectionTypeNode(array_map(fn ($t) => self::resolveTypeNodeAst($t, $ref), $n->types)); } return $n; } - - /** - * Returns shared static instances of PHPStan's TypeParser and Lexer. - * - * @return array{TypeParser, Lexer} - */ - private static function getTypeParserComponents(): array - { - /** @var TypeParser|null $typeParser */ - static $typeParser = null; - /** @var Lexer|null $lexer */ - static $lexer = null; - - if ($typeParser === null || $lexer === null) { - $configParser = new ParserConfig(usedAttributes: []); - $lexer = new Lexer($configParser); - $constExprParser = new ConstExprParser($configParser); - $typeParser = new TypeParser($configParser, $constExprParser); - } - - return [$typeParser, $lexer]; - } } diff --git a/src/Resolver/TemplateSubstitutor.php b/src/Internal/Generics/TemplateSubstitutor.php similarity index 99% rename from src/Resolver/TemplateSubstitutor.php rename to src/Internal/Generics/TemplateSubstitutor.php index b8860ed..ef7654a 100644 --- a/src/Resolver/TemplateSubstitutor.php +++ b/src/Internal/Generics/TemplateSubstitutor.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Resolver; +namespace TypePHP\Internal\Generics; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; diff --git a/src/Internal/CacheManager.php b/src/Internal/Io/CacheManager.php similarity index 98% rename from src/Internal/CacheManager.php rename to src/Internal/Io/CacheManager.php index 8846b8f..c067495 100644 --- a/src/Internal/CacheManager.php +++ b/src/Internal/Io/CacheManager.php @@ -2,9 +2,10 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Io; -use TypePHP\Contract\FileFilter; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; /** * @internal diff --git a/src/Internal/StreamWrapper.php b/src/Internal/Io/StreamWrapper.php similarity index 87% rename from src/Internal/StreamWrapper.php rename to src/Internal/Io/StreamWrapper.php index d994574..b411501 100644 --- a/src/Internal/StreamWrapper.php +++ b/src/Internal/Io/StreamWrapper.php @@ -2,14 +2,19 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Io; -require_once __DIR__ . '/PathMatcher.php'; +require_once __DIR__ . '/../Util/PathMatcher.php'; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\CloningVisitor; +use PhpParser\Parser; use PhpParser\ParserFactory; -use TypePHP\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Ast\ContractVisitor; +use TypePHP\Internal\Ast\TypePHPPrinter; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\PathMatcher; /** * Intercepts PHP 'file://' protocol operations to perform on-the-fly AST transformations @@ -45,6 +50,21 @@ final class StreamWrapper implements StreamWrapperInterface private static bool $cacheEnabled = true; + /** + * Reusable AST parser singleton. + */ + private static ?Parser $parser = null; + + /** + * Reusable AST printer singleton. + */ + private static ?TypePHPPrinter $printer = null; + + /** + * Reusable AST cloning visitor singleton. + */ + private static ?CloningVisitor $cloningVisitor = null; + /** * In-memory cache for positive url_stat results. * @@ -80,6 +100,30 @@ final class StreamWrapper implements StreamWrapperInterface 'token_get_all' => true, ]; + /** + * Returns a shared singleton instance of the PHP-Parser parser. + */ + public static function getParser(): Parser + { + return self::$parser ??= (new ParserFactory())->createForNewestSupportedVersion(); + } + + /** + * Returns a shared singleton instance of the TypePHP format-preserving printer. + */ + public static function getPrinter(): TypePHPPrinter + { + return self::$printer ??= new TypePHPPrinter(); + } + + /** + * Returns a shared singleton instance of the AST CloningVisitor. + */ + public static function getCloningVisitor(): CloningVisitor + { + return self::$cloningVisitor ??= new CloningVisitor(); + } + /** * Resets all internal in-memory caches and path matchers. */ @@ -140,7 +184,7 @@ public static function transformSource(string $source, string $filePath = ''): s $originalLineCount = substr_count($source, "\n"); - $parser = (new ParserFactory())->createForNewestSupportedVersion(); + $parser = self::getParser(); try { $oldStmts = $parser->parse($source); @@ -156,7 +200,7 @@ public static function transformSource(string $source, string $filePath = ''): s $oldTokens = $parser->getTokens(); $traverser1 = new NodeTraverser(); - $traverser1->addVisitor(new CloningVisitor()); + $traverser1->addVisitor(self::getCloningVisitor()); /** @var array<\PhpParser\Node\Stmt> $nodesToTraverse */ $nodesToTraverse = $oldStmts; @@ -166,10 +210,10 @@ public static function transformSource(string $source, string $filePath = ''): s $traverser2->addVisitor(new ContractVisitor()); $newStmts = $traverser2->traverse($newStmts); - $printer = new TypePHPPrinter(); + $printer = self::getPrinter(); $transformed = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens); - $transformed = preg_replace('/(?:\/\/(.*?)|#(.*?))(?=[ \t]*\r?\n[ \t]*\/\*__TYPEPHP_INJECTED_START__\*\/)/', '/*$1$2 */', $transformed) ?? $transformed; + $transformed = self::neutralizeTrailingLineComments($transformed); $transformed = preg_replace('/[ \t]*\r?\n[ \t]*\/\*__TYPEPHP_INJECTED_START__\*\//', ' /*__TYPEPHP_INJECTED_START__*/', $transformed) ?? $transformed; $transformedLineCount = substr_count($transformed, "\n"); @@ -194,6 +238,58 @@ public static function transformSource(string $source, string $filePath = ''): s return str_replace(['/*__TYPEPHP_INJECTED_START__*/', '/*__TYPEPHP_INJECTED_END__*/'], '', $transformed); } + /** + * Safely neutralizes trailing single-line comments (// or #) preceding an injected check + * into block comments without corrupting string literals containing '//' or '#'. + */ + private static function neutralizeTrailingLineComments(string $code): string + { + if (! str_contains($code, '/*__TYPEPHP_INJECTED_START__*/')) { + return $code; + } + + try { + $tokens = \PhpToken::tokenize($code); + $count = \count($tokens); + $result = ''; + + for ($i = 0; $i < $count; $i++) { + $token = $tokens[$i]; + + if ($token->id === T_COMMENT && (str_starts_with($token->text, '//') || str_starts_with($token->text, '#'))) { + $isPrecedingInjection = false; + + for ($j = $i + 1; $j < $count; $j++) { + $next = $tokens[$j]; + if ($next->id === T_WHITESPACE) { + continue; + } + if ($next->id === T_COMMENT && str_starts_with($next->text, '/*__TYPEPHP_INJECTED_START__*/')) { + $isPrecedingInjection = true; + } + + break; + } + + if ($isPrecedingInjection) { + $commentText = rtrim($token->text, "\r\n"); + $trailingNewlines = substr($token->text, \strlen($commentText)); + $cleaned = ltrim($commentText, '/# '); + $result .= '/* ' . $cleaned . ' */' . $trailingNewlines; + + continue; + } + } + + $result .= $token->text; + } + + return $result; + } catch (\Throwable $e) { + return $code; + } + } + /** * Opens a file stream using a multi-stage validation pipeline: * 1. Rejects non-include operations, non-read modes, and non-PHP files directly. diff --git a/src/Internal/StreamWrapperInterface.php b/src/Internal/Io/StreamWrapperInterface.php similarity index 98% rename from src/Internal/StreamWrapperInterface.php rename to src/Internal/Io/StreamWrapperInterface.php index 4eeb3cc..a1f97ef 100644 --- a/src/Internal/StreamWrapperInterface.php +++ b/src/Internal/Io/StreamWrapperInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Io; /** * @internal Contract defining PHP's native stream wrapper protocol methods. diff --git a/src/Contract/HierarchyResolver.php b/src/Internal/Resolver/HierarchyResolver.php similarity index 99% rename from src/Contract/HierarchyResolver.php rename to src/Internal/Resolver/HierarchyResolver.php index e14b714..89dcd21 100644 --- a/src/Contract/HierarchyResolver.php +++ b/src/Internal/Resolver/HierarchyResolver.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Contract; +namespace TypePHP\Internal\Resolver; use ReflectionClass; use ReflectionMethod; diff --git a/src/Resolver/SpecialTypeResolver.php b/src/Internal/Resolver/SpecialTypeResolver.php similarity index 98% rename from src/Resolver/SpecialTypeResolver.php rename to src/Internal/Resolver/SpecialTypeResolver.php index 1026bb2..c590318 100644 --- a/src/Resolver/SpecialTypeResolver.php +++ b/src/Internal/Resolver/SpecialTypeResolver.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Resolver; +namespace TypePHP\Internal\Resolver; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; @@ -26,10 +26,10 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; +use TypePHP\Internal\Util\ClassNameValidator; /** * Resolves special type identifiers (self, static, parent, FQCNs) against Reflection or file contexts. @@ -174,6 +174,13 @@ public static function checkThisIdentity(TypeNode $returnTypeNode, mixed $value, */ public static function resolve(TypeNode $node, \ReflectionClass|\ReflectionFunction|\ReflectionMethod|string $context, ?object $thisObj = null): TypeNode { + if ($node instanceof IdentifierTypeNode) { + $lower = strtolower($node->name); + if (isset(self::BUILTIN_TYPE_KEYWORDS[$lower]) && $lower !== 'self' && $lower !== 'parent' && $lower !== 'static' && $lower !== '$this') { + return $node; + } + } + $ref = self::getReflectionContext($context); $declaringClass = $ref instanceof \ReflectionMethod ? $ref->getDeclaringClass()->getName() : ($ref instanceof \ReflectionClass ? $ref->getName() : null); @@ -1101,7 +1108,7 @@ public static function parseFileMetadata(string $fileName, string $source): void if (($token->id === T_CLASS || $token->id === T_INTERFACE || $token->id === T_TRAIT || (\defined('T_ENUM') && $token->id === T_ENUM)) && isset($tokens[$i + 2]) && $tokens[$i + 2]->id === T_STRING) { $className = $tokens[$i + 2]->text; $currentClass = $namespace !== '' ? $namespace . '\\' . $className : $className; - self::$classTraitUseDocs[$currentClass] ??= []; + self::$classTraitUseDocs[$currentClass] = []; continue; } diff --git a/src/Internal/RuntimeTypeChecker.php b/src/Internal/RuntimeTypeChecker.php index bcf3c77..ad031b6 100644 --- a/src/Internal/RuntimeTypeChecker.php +++ b/src/Internal/RuntimeTypeChecker.php @@ -6,15 +6,18 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Contract\ContractParser; +use TypePHP\Internal\Ast\ScopeCleaner; use TypePHP\Internal\Checker\GeneratorChecker; use TypePHP\Internal\Checker\InlineChecker; use TypePHP\Internal\Checker\ParamChecker; use TypePHP\Internal\Checker\ReturnChecker; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Validator\TypeValidatorRegistry; -use TypePHP\Wrapper\CallableWrapper; -use TypePHP\Wrapper\IterableWrapper; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Wrapper\CallableWrapper; +use TypePHP\Internal\Wrapper\IterableWrapper; /** * Core runtime type checking engine facade for parameter validation, return type enforcement, and variable tracking. @@ -83,7 +86,7 @@ public static function setupScope(string $function, array $vars, object|string|n $err = ParamChecker::checkParams($function, $vars, $thisOrClass, self::getRegistry(), $effectiveFunction); - $contract = ContractParser::parse($effectiveFunction); + $contract = DocblockParser::parse($effectiveFunction); $methodTemplates = $contract['templates'] ?? []; $hasMethodTemplates = \count($methodTemplates) > 0; diff --git a/src/Internal/ClassNameValidator.php b/src/Internal/Util/ClassNameValidator.php similarity index 98% rename from src/Internal/ClassNameValidator.php rename to src/Internal/Util/ClassNameValidator.php index f2df59a..b0717e4 100644 --- a/src/Internal/ClassNameValidator.php +++ b/src/Internal/Util/ClassNameValidator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Util; /** * @internal diff --git a/src/Internal/Config.php b/src/Internal/Util/Config.php similarity index 97% rename from src/Internal/Config.php rename to src/Internal/Util/Config.php index 9d5bb16..bf9baa4 100644 --- a/src/Internal/Config.php +++ b/src/Internal/Util/Config.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Util; -use TypePHP\Contract\ContractParser; -use TypePHP\Contract\FileFilter; -use TypePHP\Contract\HierarchyResolver; use TypePHP\Extension\ExtensionInterface; -use TypePHP\Extension\ExtensionManager; use TypePHP\Internal\Checker\ParamChecker; use TypePHP\Internal\Checker\ReturnChecker; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Io\CacheManager; +use TypePHP\Internal\Io\StreamWrapper; +use TypePHP\Internal\Resolver\HierarchyResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; /** * Global configuration manager for loading and dynamically overriding settings. @@ -295,7 +295,7 @@ public static function set(array $config): void self::$cachedConfig = $mergedConfig; self::syncFlags($mergedConfig); - ContractParser::reset(); + DocblockParser::reset(); ParamChecker::reset(); ReturnChecker::reset(); FileFilter::reset(); @@ -354,7 +354,7 @@ public static function reset(): void self::$respectNativeNullability = true; self::$arrayValidation = 'full'; - ContractParser::reset(); + DocblockParser::reset(); ParamChecker::reset(); ReturnChecker::reset(); TemplateManager::reset(); diff --git a/src/Extension/ExtensionManager.php b/src/Internal/Util/ExtensionManager.php similarity index 97% rename from src/Extension/ExtensionManager.php rename to src/Internal/Util/ExtensionManager.php index 41aaa67..38b33c8 100644 --- a/src/Extension/ExtensionManager.php +++ b/src/Internal/Util/ExtensionManager.php @@ -2,7 +2,9 @@ declare(strict_types=1); -namespace TypePHP\Extension; +namespace TypePHP\Internal\Util; + +use TypePHP\Extension\ExtensionInterface; /** * Loads explicitly registered TypePHP extensions from user configuration. diff --git a/src/Contract/FileFilter.php b/src/Internal/Util/FileFilter.php similarity index 95% rename from src/Contract/FileFilter.php rename to src/Internal/Util/FileFilter.php index c6d1189..d097eeb 100644 --- a/src/Contract/FileFilter.php +++ b/src/Internal/Util/FileFilter.php @@ -2,10 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Contract; - -use TypePHP\Internal\Config; -use TypePHP\Internal\PathMatcher; +namespace TypePHP\Internal\Util; /** * @internal Checks file paths against vendor directories, file extensions, and user-configured include/exclude globs. diff --git a/src/Internal/PathMatcher.php b/src/Internal/Util/PathMatcher.php similarity index 97% rename from src/Internal/PathMatcher.php rename to src/Internal/Util/PathMatcher.php index 98958db..b9bcf43 100644 --- a/src/Internal/PathMatcher.php +++ b/src/Internal/Util/PathMatcher.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Util; -require_once __DIR__ . '/CacheManager.php'; +use TypePHP\Internal\Io\CacheManager; + +require_once __DIR__ . '/../Io/CacheManager.php'; /** * Centralized utility for path normalization, glob compilation, vendor isolation, and specificity matching. @@ -188,7 +190,7 @@ public static function isCachePath(string $normalizedPath): bool public static function isLibraryInternal(string $normalizedPath): bool { if (self::$cachedLibSrcDir === null) { - $parentDir = realpath(__DIR__ . '/..'); + $parentDir = realpath(\dirname(__DIR__, 2)); self::$cachedLibSrcDir = $parentDir !== false ? rtrim(self::normalizePath($parentDir), '/') . '/' : ''; } @@ -208,14 +210,8 @@ public static function isLibraryInternal(string $normalizedPath): bool if (str_starts_with($lowerCanon, $lowerLibSrcDir)) { $internalDirs = [ $lowerLibSrcDir . 'internal/', - $lowerLibSrcDir . 'contract/', - $lowerLibSrcDir . 'command/', - $lowerLibSrcDir . 'validator/', - $lowerLibSrcDir . 'wrapper/', - $lowerLibSrcDir . 'resolver/', $lowerLibSrcDir . 'extension/', $lowerLibSrcDir . 'exception/', - $lowerLibSrcDir . 'compiler/', $lowerLibSrcDir . 'typephp.php', $lowerLibSrcDir . 'bootstrap.php', ]; diff --git a/src/Internal/StubManager.php b/src/Internal/Util/StubManager.php similarity index 98% rename from src/Internal/StubManager.php rename to src/Internal/Util/StubManager.php index 507bb07..25a497e 100644 --- a/src/Internal/StubManager.php +++ b/src/Internal/Util/StubManager.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace TypePHP\Internal; +namespace TypePHP\Internal\Util; use FilesystemIterator; use PhpParser\Node; -use PhpParser\ParserFactory; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use SplFileInfo; use Throwable; +use TypePHP\Internal\Io\StreamWrapper; /** * @internal Indexes and resolves DocBlock stub overrides for third-party classes, methods, properties, and functions. @@ -136,7 +136,7 @@ public static function hasFunctionStub(string $functionName): bool private static function loadStubFiles(array $globs): void { $projectRoot = Config::getProjectRoot(); - $parser = (new ParserFactory())->createForNewestSupportedVersion(); + $parser = StreamWrapper::getParser(); foreach ($globs as $pattern) { $files = self::resolveStubFiles($pattern, $projectRoot); diff --git a/src/Validator/ArrayShapeValidator.php b/src/Internal/Validator/ArrayShapeValidator.php similarity index 95% rename from src/Validator/ArrayShapeValidator.php rename to src/Internal/Validator/ArrayShapeValidator.php index b8db790..df334cd 100644 --- a/src/Validator/ArrayShapeValidator.php +++ b/src/Internal/Validator/ArrayShapeValidator.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; /** * @internal Class for validating array shapes and tuple shapes like array{0: string, 1: int} or array{string, int}. diff --git a/src/Validator/ArrayValidator.php b/src/Internal/Validator/ArrayValidator.php similarity index 94% rename from src/Validator/ArrayValidator.php rename to src/Internal/Validator/ArrayValidator.php index 19f98d4..aefcd13 100644 --- a/src/Validator/ArrayValidator.php +++ b/src/Internal/Validator/ArrayValidator.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use Generator; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use Traversable; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; +use TypePHP\Internal\Util\Config; /** * Validates array and Traversable collection instances against ArrayTypeNode ASTs (Type[]). diff --git a/src/Validator/ConstValidator.php b/src/Internal/Validator/ConstValidator.php similarity index 96% rename from src/Validator/ConstValidator.php rename to src/Internal/Validator/ConstValidator.php index 0d17231..df989bd 100644 --- a/src/Validator/ConstValidator.php +++ b/src/Internal/Validator/ConstValidator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; @@ -13,9 +13,9 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; /** * @internal Validates literal values, class constants, and wildcard constant patterns (Class::PREFIX_*) against ConstTypeNode ASTs. diff --git a/src/Validator/GenericValidator.php b/src/Internal/Validator/GenericValidator.php similarity index 98% rename from src/Validator/GenericValidator.php rename to src/Internal/Validator/GenericValidator.php index 1a123d8..ada7176 100644 --- a/src/Validator/GenericValidator.php +++ b/src/Internal/Validator/GenericValidator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; @@ -14,12 +14,12 @@ use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; use TypePHP\Internal\RuntimeTypeChecker; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Util\ClassNameValidator; +use TypePHP\Internal\Util\Config; /** * @internal Validates values against generic AST structures (int ranges, class-string, list, array, object generics, key-of, value-of, int-mask, int-mask-of). diff --git a/src/Validator/IdentifierValidator.php b/src/Internal/Validator/IdentifierValidator.php similarity index 94% rename from src/Validator/IdentifierValidator.php rename to src/Internal/Validator/IdentifierValidator.php index 4a28877..baef695 100644 --- a/src/Validator/IdentifierValidator.php +++ b/src/Internal/Validator/IdentifierValidator.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ClassNameValidator; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; -use TypePHP\Wrapper\CallableWrapper; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; +use TypePHP\Internal\Util\ClassNameValidator; +use TypePHP\Internal\Wrapper\CallableWrapper; /** * @internal Class for validating basic scalar identifier types like int, string, bool, array, list, object, callable, resource, null, true, false, mixed, scalar, void. diff --git a/src/Validator/IntersectionValidator.php b/src/Internal/Validator/IntersectionValidator.php similarity index 89% rename from src/Validator/IntersectionValidator.php rename to src/Internal/Validator/IntersectionValidator.php index e213323..f3f78a9 100644 --- a/src/Validator/IntersectionValidator.php +++ b/src/Internal/Validator/IntersectionValidator.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; /** * @internal Class for validating intersection types like int & string. diff --git a/src/Validator/NullableValidator.php b/src/Internal/Validator/NullableValidator.php similarity index 87% rename from src/Validator/NullableValidator.php rename to src/Internal/Validator/NullableValidator.php index 81dec98..941058e 100644 --- a/src/Validator/NullableValidator.php +++ b/src/Internal/Validator/NullableValidator.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; /** * @internal Class for validating nullable types like ?int. diff --git a/src/Validator/ObjectShapeValidator.php b/src/Internal/Validator/ObjectShapeValidator.php similarity index 94% rename from src/Validator/ObjectShapeValidator.php rename to src/Internal/Validator/ObjectShapeValidator.php index 68b65ec..a4863aa 100644 --- a/src/Validator/ObjectShapeValidator.php +++ b/src/Internal/Validator/ObjectShapeValidator.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; /** * @internal Validates stdClass dynamic properties and custom class instances against PHPDoc object shape structures. diff --git a/src/Validator/TypeValidatorInterface.php b/src/Internal/Validator/TypeValidatorInterface.php similarity index 84% rename from src/Validator/TypeValidatorInterface.php rename to src/Internal/Validator/TypeValidatorInterface.php index 08ad6ba..74d282d 100644 --- a/src/Validator/TypeValidatorInterface.php +++ b/src/Internal/Validator/TypeValidatorInterface.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; /** * Strategy interface for validating values against specific PHPDoc AST TypeNodes. diff --git a/src/Validator/TypeValidatorRegistry.php b/src/Internal/Validator/TypeValidatorRegistry.php similarity index 53% rename from src/Validator/TypeValidatorRegistry.php rename to src/Internal/Validator/TypeValidatorRegistry.php index cdf2d3a..8ca9a8a 100644 --- a/src/Validator/TypeValidatorRegistry.php +++ b/src/Internal/Validator/TypeValidatorRegistry.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; @@ -14,7 +14,7 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; /** * Registry mapping AST TypeNodes to their corresponding validator strategy implementations. @@ -23,10 +23,21 @@ final class TypeValidatorRegistry { private IdentifierValidator $identifierValidator; - /** - * @var array - */ - private array $validators = []; + private GenericValidator $genericValidator; + + private UnionValidator $unionValidator; + + private IntersectionValidator $intersectionValidator; + + private NullableValidator $nullableValidator; + + private ArrayValidator $arrayValidator; + + private ArrayShapeValidator $arrayShapeValidator; + + private ObjectShapeValidator $objectShapeValidator; + + private ConstValidator $constValidator; /** * WeakMap memoizing previously validated object instances against TypeNode signatures. @@ -46,17 +57,14 @@ public static function reset(): void public function __construct() { $this->identifierValidator = new IdentifierValidator(); - $this->validators = [ - IdentifierTypeNode::class => $this->identifierValidator, - GenericTypeNode::class => new GenericValidator(), - UnionTypeNode::class => new UnionValidator(), - IntersectionTypeNode::class => new IntersectionValidator(), - NullableTypeNode::class => new NullableValidator(), - ArrayTypeNode::class => new ArrayValidator(), - ArrayShapeNode::class => new ArrayShapeValidator(), - ObjectShapeNode::class => new ObjectShapeValidator(), - ConstTypeNode::class => new ConstValidator(), - ]; + $this->genericValidator = new GenericValidator(); + $this->unionValidator = new UnionValidator(); + $this->intersectionValidator = new IntersectionValidator(); + $this->nullableValidator = new NullableValidator(); + $this->arrayValidator = new ArrayValidator(); + $this->arrayShapeValidator = new ArrayShapeValidator(); + $this->objectShapeValidator = new ObjectShapeValidator(); + $this->constValidator = new ConstValidator(); } /** @@ -77,15 +85,18 @@ public function validate(mixed $value, TypeNode $node, string $context = ''): ?E } } - if ($node instanceof IdentifierTypeNode) { - $err = $this->identifierValidator->validate($value, $node, $context, $this); - } else { - $validator = $this->validators[\get_class($node)] ?? null; - if ($validator === null) { - return null; - } - $err = $validator->validate($value, $node, $context, $this); - } + $err = match ($node::class) { + IdentifierTypeNode::class => $this->identifierValidator->validate($value, $node, $context, $this), + GenericTypeNode::class => $this->genericValidator->validate($value, $node, $context, $this), + UnionTypeNode::class => $this->unionValidator->validate($value, $node, $context, $this), + NullableTypeNode::class => $this->nullableValidator->validate($value, $node, $context, $this), + ArrayTypeNode::class => $this->arrayValidator->validate($value, $node, $context, $this), + ArrayShapeNode::class => $this->arrayShapeValidator->validate($value, $node, $context, $this), + ObjectShapeNode::class => $this->objectShapeValidator->validate($value, $node, $context, $this), + IntersectionTypeNode::class => $this->intersectionValidator->validate($value, $node, $context, $this), + ConstTypeNode::class => $this->constValidator->validate($value, $node, $context, $this), + default => null, + }; if ($err === null && $isObj && $nodeKey !== null) { $cache = self::$validatedObjectCache[$value] ?? []; diff --git a/src/Validator/UnionValidator.php b/src/Internal/Validator/UnionValidator.php similarity index 89% rename from src/Validator/UnionValidator.php rename to src/Internal/Validator/UnionValidator.php index 3c766d9..0bef7d5 100644 --- a/src/Validator/UnionValidator.php +++ b/src/Internal/Validator/UnionValidator.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace TypePHP\Validator; +namespace TypePHP\Internal\Validator; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Diagnostic\TypeFormatter; /** * @internal Class for validating union types like int | string. diff --git a/src/Wrapper/CallableWrapper.php b/src/Internal/Wrapper/CallableWrapper.php similarity index 95% rename from src/Wrapper/CallableWrapper.php rename to src/Internal/Wrapper/CallableWrapper.php index 0a6f40d..56c6e53 100644 --- a/src/Wrapper/CallableWrapper.php +++ b/src/Internal/Wrapper/CallableWrapper.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Wrapper; +namespace TypePHP\Internal\Wrapper; use Closure; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; @@ -12,14 +12,14 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use ReflectionFunction; use TypeError; -use TypePHP\Contract\ContractParser; use TypePHP\Exception\TypeError as TypePHPTypeError; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\TypeFormatter; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\TypeFormatter; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * Wraps callables to enforce argument and return type contracts dynamically at runtime. @@ -70,7 +70,7 @@ public static function isCallable(mixed $value): bool */ public static function wrap(string $function, string $paramName, mixed $callable, TypeValidatorRegistry $registry, object|string|null $thisOrClass = null): mixed { - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $typeNode = ($paramName === 'return') ? ($contract['return'] ?? null) : ($contract['types'][$paramName] ?? null); $aliases = $contract['aliases'] ?? []; $templates = [...($contract['classTemplates'] ?? []), ...($contract['templates'] ?? [])]; diff --git a/src/Wrapper/IterableWrapper.php b/src/Internal/Wrapper/IterableWrapper.php similarity index 93% rename from src/Wrapper/IterableWrapper.php rename to src/Internal/Wrapper/IterableWrapper.php index 2943ba7..016ef82 100644 --- a/src/Wrapper/IterableWrapper.php +++ b/src/Internal/Wrapper/IterableWrapper.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace TypePHP\Wrapper; +namespace TypePHP\Internal\Wrapper; use Generator; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use TypePHP\Contract\ContractParser; use TypePHP\Exception\TypeError as TypePHPTypeError; -use TypePHP\Internal\ErrorFactory; -use TypePHP\Resolver\SpecialTypeResolver; -use TypePHP\Resolver\TemplateManager; -use TypePHP\Resolver\TemplateSubstitutor; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Generics\TemplateSubstitutor; +use TypePHP\Internal\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * Wraps Traversable objects and Generators to evaluate key and value type constraints lazily during iteration. @@ -37,7 +37,7 @@ public static function wrap(string $function, string $paramName, mixed $iterable return $iterable; } - $contract = ContractParser::parse($function); + $contract = DocblockParser::parse($function); $typeNode = ($paramName === 'return') ? ($contract['return'] ?? null) : ($contract['types'][$paramName] ?? null); if ($typeNode === null) { diff --git a/src/Wrapper/IteratorProxy.php b/src/Internal/Wrapper/IteratorProxy.php similarity index 98% rename from src/Wrapper/IteratorProxy.php rename to src/Internal/Wrapper/IteratorProxy.php index 7ddd831..381193d 100644 --- a/src/Wrapper/IteratorProxy.php +++ b/src/Internal/Wrapper/IteratorProxy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace TypePHP\Wrapper; +namespace TypePHP\Internal\Wrapper; use Countable; use Iterator; diff --git a/src/TypePHP.php b/src/TypePHP.php index 813038c..6111698 100644 --- a/src/TypePHP.php +++ b/src/TypePHP.php @@ -4,9 +4,9 @@ namespace TypePHP; -use TypePHP\Internal\Config; -use TypePHP\Internal\StreamWrapper; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Io\StreamWrapper; +use TypePHP\Internal\Util\Config; final class TypePHP { diff --git a/tests/Contract/DocblockExtractorTest.php b/tests/Contract/DocblockExtractorTest.php index 56df5b7..3d41fbf 100644 --- a/tests/Contract/DocblockExtractorTest.php +++ b/tests/Contract/DocblockExtractorTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; -use TypePHP\Contract\DocblockExtractor; +use TypePHP\Internal\Docblock\DocblockExtractor; use TypePHP\Tests\Fixtures\Services\HelperService; use TypePHP\Tests\Fixtures\Services\UserService; use TypePHP\Tests\Fixtures\Shopware\Metric\Type as MetricTypeEnum; @@ -274,7 +274,7 @@ class HelperService file_put_contents($stubPath, $stubContent); try { - TypePHP\Internal\Config::set([ + TypePHP\Internal\Util\Config::set([ 'stubs' => [ str_replace('\\', '/', $tempDir) . '/**', ], @@ -293,7 +293,7 @@ class HelperService if (is_dir($tempDir)) { @rmdir($tempDir); } - TypePHP\Internal\Config::reset(); + TypePHP\Internal\Util\Config::reset(); } }); diff --git a/tests/Contract/FileFilterTest.php b/tests/Contract/FileFilterTest.php index 15c178d..61556f2 100644 --- a/tests/Contract/FileFilterTest.php +++ b/tests/Contract/FileFilterTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use TypePHP\Contract\FileFilter; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; describe('FileFilter Unit Tests', function () { test('returns false for null, empty, or false file paths', function () { diff --git a/tests/Contract/HierarchyResolverTest.php b/tests/Contract/HierarchyResolverTest.php index 78b42c0..4a3414d 100644 --- a/tests/Contract/HierarchyResolverTest.php +++ b/tests/Contract/HierarchyResolverTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Contract\HierarchyResolver; +use TypePHP\Internal\Resolver\HierarchyResolver; use TypePHP\Tests\Fixtures\Services\BaseService; use TypePHP\Tests\Fixtures\Services\UserService; use TypePHP\Tests\Fixtures\Types\CountableArrayAccess; diff --git a/tests/Contract/PackagePathInclusionTest.php b/tests/Contract/PackagePathInclusionTest.php index efd94fc..3e93215 100644 --- a/tests/Contract/PackagePathInclusionTest.php +++ b/tests/Contract/PackagePathInclusionTest.php @@ -4,21 +4,17 @@ namespace TypePHP\Tests\Contract; -use TypePHP\Contract\FileFilter; -use TypePHP\Internal\Config; -use TypePHP\Internal\PathMatcher; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; +use TypePHP\Internal\Util\PathMatcher; describe('Packages and Monorepo Path Inclusion (packages/**/src/**)', function () { beforeEach(function () { Config::reset(); - FileFilter::reset(); - PathMatcher::reset(); }); afterEach(function () { Config::reset(); - FileFilter::reset(); - PathMatcher::reset(); }); test('correctly includes package source files with packages/**/src/** glob', function () { diff --git a/tests/Contract/VendorIsolationPathTest.php b/tests/Contract/VendorIsolationPathTest.php index 1e5b5f2..2f3f6ab 100644 --- a/tests/Contract/VendorIsolationPathTest.php +++ b/tests/Contract/VendorIsolationPathTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use TypePHP\Contract\FileFilter; -use TypePHP\Internal\Config; -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; describe('Vendor Path Isolation & Whitelisting (Shopware Doctrine DBAL Reproduction)', function () { beforeEach(function () { diff --git a/tests/Extension/ExtensionManagerTest.php b/tests/Extension/ExtensionManagerTest.php index e182708..936b42f 100644 --- a/tests/Extension/ExtensionManagerTest.php +++ b/tests/Extension/ExtensionManagerTest.php @@ -4,9 +4,9 @@ namespace TypePHP\Tests\Extension; -use TypePHP\Contract\FileFilter; -use TypePHP\Extension\ExtensionManager; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\ExtensionManager; +use TypePHP\Internal\Util\FileFilter; use TypePHP\Tests\Fixtures\Extensions\SampleRegisteredExtension; describe('ExtensionManager Unit Tests', function () { diff --git a/tests/Visitor/FunctionContractInjectorTest.php b/tests/Internal/Ast/FunctionContractInjectorTest.php similarity index 97% rename from tests/Visitor/FunctionContractInjectorTest.php rename to tests/Internal/Ast/FunctionContractInjectorTest.php index 5ccc2b9..2f6c8ea 100644 --- a/tests/Visitor/FunctionContractInjectorTest.php +++ b/tests/Internal/Ast/FunctionContractInjectorTest.php @@ -4,8 +4,8 @@ use PhpParser\Comment\Doc; use PhpParser\Node; -use TypePHP\Internal\Config; -use TypePHP\Internal\Visitor\FunctionContractInjector; +use TypePHP\Internal\Ast\FunctionContractInjector; +use TypePHP\Internal\Util\Config; describe('FunctionContractInjector Unit Tests', function () { beforeEach(function () { @@ -60,7 +60,7 @@ public function dd(): never } PHP; - $transformed = TypePHP\Internal\StreamWrapper::transformSource($source, 'test_never_method.php'); + $transformed = TypePHP\Internal\Io\StreamWrapper::transformSource($source, 'test_never_method.php'); expect($transformed)->toContain('RuntimeTypeChecker::setupScope') ->and($transformed)->not()->toContain('return ($__typephpRet') @@ -273,7 +273,7 @@ public function dd(): never }); describe('Ignore Tag Suppression (@typephp-ignore)', function () { - test('injects setupScope hook so @typephp-ignore can be resolved dynamically at runtime by ContractParser', function () { + test('injects setupScope hook so @typephp-ignore can be resolved dynamically at runtime by DocblockParser', function () { $doc = new Doc("/**\n * @typephp-ignore\n * @param positive-int \$id\n */"); $method = new Node\Stmt\ClassMethod('ignoredMethod', [ diff --git a/tests/Visitor/NodeBuilderTest.php b/tests/Internal/Ast/NodeBuilderTest.php similarity index 97% rename from tests/Visitor/NodeBuilderTest.php rename to tests/Internal/Ast/NodeBuilderTest.php index fbbda9d..8192220 100644 --- a/tests/Visitor/NodeBuilderTest.php +++ b/tests/Internal/Ast/NodeBuilderTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use PhpParser\Node; -use TypePHP\Internal\Visitor\NodeBuilder; +use TypePHP\Internal\Ast\NodeBuilder; describe('NodeBuilder Unit Tests', function () { test('createPropertyCheckCall creates FuncCall node for RuntimeTypeChecker::checkProperty', function () { diff --git a/tests/Visitor/PropertyHookInjectorTest.php b/tests/Internal/Ast/PropertyHookInjectorTest.php similarity index 98% rename from tests/Visitor/PropertyHookInjectorTest.php rename to tests/Internal/Ast/PropertyHookInjectorTest.php index 71aaccc..e61fb9b 100644 --- a/tests/Visitor/PropertyHookInjectorTest.php +++ b/tests/Internal/Ast/PropertyHookInjectorTest.php @@ -8,8 +8,8 @@ use PhpParser\Comment\Doc; use PhpParser\Node; -use TypePHP\Internal\Config; -use TypePHP\Internal\Visitor\PropertyHookInjector; +use TypePHP\Internal\Ast\PropertyHookInjector; +use TypePHP\Internal\Util\Config; describe('PropertyHookInjector Unit Tests', function () { beforeEach(function () { diff --git a/tests/Visitor/ScopeManagerTest.php b/tests/Internal/Ast/ScopeManagerTest.php similarity index 98% rename from tests/Visitor/ScopeManagerTest.php rename to tests/Internal/Ast/ScopeManagerTest.php index 005a8a4..43ca448 100644 --- a/tests/Visitor/ScopeManagerTest.php +++ b/tests/Internal/Ast/ScopeManagerTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use PhpParser\Node; -use TypePHP\Internal\Visitor\ScopeManager; +use TypePHP\Internal\Ast\ScopeManager; describe('ScopeManager Unit Tests', function () { test('manages scoped variable stack frames', function () { diff --git a/tests/Internal/TypeFormatterTest.php b/tests/Internal/Ast/TypeFormatterTest.php similarity index 97% rename from tests/Internal/TypeFormatterTest.php rename to tests/Internal/Ast/TypeFormatterTest.php index 0a46c82..de582f5 100644 --- a/tests/Internal/TypeFormatterTest.php +++ b/tests/Internal/Ast/TypeFormatterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\TypeFormatter; +use TypePHP\Internal\Diagnostic\TypeFormatter; test('formats negative integer correctly', function () { expect(TypeFormatter::formatGivenValue(-10))->toBe('negative int (-10)'); diff --git a/tests/RuntimeChecker/GeneratorCheckerTest.php b/tests/Internal/Checker/GeneratorCheckerTest.php similarity index 96% rename from tests/RuntimeChecker/GeneratorCheckerTest.php rename to tests/Internal/Checker/GeneratorCheckerTest.php index 520ba67..e0850c4 100644 --- a/tests/RuntimeChecker/GeneratorCheckerTest.php +++ b/tests/Internal/Checker/GeneratorCheckerTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use TypePHP\Internal\Checker\GeneratorChecker; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Validator\TypeValidatorRegistry; /** * @return Generator diff --git a/tests/RuntimeChecker/InlineCheckerTest.php b/tests/Internal/Checker/InlineCheckerTest.php similarity index 98% rename from tests/RuntimeChecker/InlineCheckerTest.php rename to tests/Internal/Checker/InlineCheckerTest.php index 76f7d8e..236ab5e 100644 --- a/tests/RuntimeChecker/InlineCheckerTest.php +++ b/tests/Internal/Checker/InlineCheckerTest.php @@ -3,16 +3,16 @@ declare(strict_types=1); use TypePHP\Internal\Checker\InlineChecker; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Generics\TemplateManager; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\GenericCollection; use TypePHP\Tests\Fixtures\Generics\HookedCollection; use TypePHP\Tests\Fixtures\Types\ConfiguredProperty; use TypePHP\TypePHP; -use TypePHP\Validator\TypeValidatorRegistry; describe('InlineChecker Unit Tests', function () { beforeEach(function () { diff --git a/tests/RuntimeChecker/ParamCheckerTest.php b/tests/Internal/Checker/ParamCheckerTest.php similarity index 98% rename from tests/RuntimeChecker/ParamCheckerTest.php rename to tests/Internal/Checker/ParamCheckerTest.php index c5a8c34..0fa87ef 100644 --- a/tests/RuntimeChecker/ParamCheckerTest.php +++ b/tests/Internal/Checker/ParamCheckerTest.php @@ -3,15 +3,15 @@ declare(strict_types=1); use TypePHP\Internal\Checker\ParamChecker; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; use TypePHP\Tests\Fixtures\Callables\GenericCallableService; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Services\ShiftedParamService; use TypePHP\Tests\Fixtures\Services\UserService; use TypePHP\Tests\Fixtures\Types\ClassStringFactoryContainer; use TypePHP\Tests\Fixtures\Types\MagicMethodFixture; -use TypePHP\Validator\TypeValidatorRegistry; describe('ParamChecker Unit Tests', function () { beforeEach(function () { diff --git a/tests/RuntimeChecker/ReturnCheckerTest.php b/tests/Internal/Checker/ReturnCheckerTest.php similarity index 98% rename from tests/RuntimeChecker/ReturnCheckerTest.php rename to tests/Internal/Checker/ReturnCheckerTest.php index 541ec7d..1631490 100644 --- a/tests/RuntimeChecker/ReturnCheckerTest.php +++ b/tests/Internal/Checker/ReturnCheckerTest.php @@ -3,8 +3,9 @@ declare(strict_types=1); use TypePHP\Internal\Checker\ReturnChecker; -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Validator\TypeValidatorRegistry; use TypePHP\Tests\Fixtures\Collections\ConcreteFileCollection; use TypePHP\Tests\Fixtures\Collections\PluginConfiguration; use TypePHP\Tests\Fixtures\Conditionals\ConditionalReturnService; @@ -14,7 +15,6 @@ use TypePHP\Tests\Fixtures\Services\UserEntityFactory; use TypePHP\Tests\Fixtures\Services\UserService; use TypePHP\Tests\Fixtures\Types\MagicMethodFixture; -use TypePHP\Validator\TypeValidatorRegistry; describe('ReturnChecker Unit Tests', function () { beforeEach(function () { diff --git a/tests/Command/CommandRunnerTest.php b/tests/Internal/Cli/CommandRunnerTest.php similarity index 98% rename from tests/Command/CommandRunnerTest.php rename to tests/Internal/Cli/CommandRunnerTest.php index 30ee72f..8e85ee0 100644 --- a/tests/Command/CommandRunnerTest.php +++ b/tests/Internal/Cli/CommandRunnerTest.php @@ -4,7 +4,7 @@ namespace TypePHP\Tests\Command; -use TypePHP\Command\CommandRunner; +use TypePHP\Internal\Cli\CommandRunner; describe('CommandRunner Unit Tests', function () { test('routes help command successfully', function () { diff --git a/tests/Internal/ErrorFactoryTest.php b/tests/Internal/Diagnostic/ErrorFactoryTest.php similarity index 91% rename from tests/Internal/ErrorFactoryTest.php rename to tests/Internal/Diagnostic/ErrorFactoryTest.php index 265015c..bfe9b36 100644 --- a/tests/Internal/ErrorFactoryTest.php +++ b/tests/Internal/Diagnostic/ErrorFactoryTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use TypePHP\Internal\ErrorFactory; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorFactory; +use TypePHP\Internal\Diagnostic\ErrorMessage; test('error factory creates an ErrorMessage value object', function () { $err = ErrorFactory::createError('Test argument error message'); diff --git a/tests/Internal/ContractParserTest.php b/tests/Internal/Docblock/ContractParserTest.php similarity index 83% rename from tests/Internal/ContractParserTest.php rename to tests/Internal/Docblock/ContractParserTest.php index 9014470..752d7e6 100644 --- a/tests/Internal/ContractParserTest.php +++ b/tests/Internal/Docblock/ContractParserTest.php @@ -17,8 +17,8 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Contract\ContractParser; -use TypePHP\Internal\Config; +use TypePHP\Internal\Docblock\DocblockParser; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\IgnoreTags\IgnoredMethod; use TypePHP\Tests\Fixtures\Services\ChildMagicMethodService; use TypePHP\Tests\Fixtures\Services\UserService; @@ -30,23 +30,23 @@ use TypePHP\Tests\Fixtures\Types\NestedAliasService; use TypePHP\Tests\Fixtures\Types\NonCpmStrings; -describe('ContractParser Unit Tests', function () { +describe('DocblockParser Unit Tests', function () { beforeEach(function () { Config::reset(); - ContractParser::reset(); + DocblockParser::reset(); }); afterEach(function () { Config::reset(); - ContractParser::reset(); + DocblockParser::reset(); }); describe('Function and Method Parsing (parse)', function () { test('parses class method contracts and caches results', function () { $target = UserService::class . '::find'; - $contract1 = ContractParser::parse($target); - $contract2 = ContractParser::parse($target); + $contract1 = DocblockParser::parse($target); + $contract2 = DocblockParser::parse($target); expect($contract1)->toBeArray() ->and($contract1['types'])->toHaveKey('id') @@ -58,7 +58,7 @@ test('parses standalone global/namespaced functions', function () { $target = 'TypePHP\Tests\Fixtures\Functions\calculateDiscount'; - $contract = ContractParser::parse($target); + $contract = DocblockParser::parse($target); expect($contract['types'])->toHaveKey('price') ->and($contract['types'])->toHaveKey('percentage') @@ -69,7 +69,7 @@ }); test('returns empty contract array for non-existent classes or functions', function () { - $contract = ContractParser::parse('NonExistentClass12345::method'); + $contract = DocblockParser::parse('NonExistentClass12345::method'); expect($contract['types'])->toBeEmpty() ->and($contract['templates'])->toBeEmpty() @@ -81,7 +81,7 @@ test('returns class-level templates and aliases when class has no requested method', function () { $target = NestedAliasService::class . '::nonExistentMethod'; - $contract = ContractParser::parse($target); + $contract = DocblockParser::parse($target); expect($contract['types'])->toBeEmpty() ->and($contract['aliases'])->toHaveKey('LocalId') @@ -91,7 +91,7 @@ test('falls back to property @var docblock for constructor property promotion', function () { $target = NonCpmStrings::class . '::__construct'; - $contract = ContractParser::parse($target); + $contract = DocblockParser::parse($target); expect($contract['types'])->toHaveKey('strings') ->and($contract['types']['strings'])->toBeInstanceOf(ArrayTypeNode::class) @@ -101,10 +101,10 @@ describe('Property Contract Parsing (parseProperty)', function () { test('parses instance and static property @var docblocks', function () { - $instanceProp = ContractParser::parseProperty(ConfiguredProperty::class, 'numbers'); + $instanceProp = DocblockParser::parseProperty(ConfiguredProperty::class, 'numbers'); expect($instanceProp)->toBeInstanceOf(ArrayTypeNode::class); - $staticProp = ContractParser::parseProperty(ConfiguredProperty::class, 'staticTitle'); + $staticProp = DocblockParser::parseProperty(ConfiguredProperty::class, 'staticTitle'); expect($staticProp)->toBeInstanceOf(IdentifierTypeNode::class) ->and($staticProp->name)->toBe('string') ; @@ -117,7 +117,7 @@ return; } - $readOnlyProp = ContractParser::parseProperty(HookedInterfaceImplementation::class, 'readOnlyProp'); + $readOnlyProp = DocblockParser::parseProperty(HookedInterfaceImplementation::class, 'readOnlyProp'); expect($readOnlyProp)->not()->toBeNull() ->and((string) $readOnlyProp)->toBe('positive-int') @@ -125,18 +125,18 @@ }); test('parses class-level magic @property docblocks', function () { - $magicScore = ContractParser::parseProperty(MagicPropertyFixture::class, 'magicScore'); + $magicScore = DocblockParser::parseProperty(MagicPropertyFixture::class, 'magicScore'); expect((string) $magicScore)->toBe('positive-int'); - $magicName = ContractParser::parseProperty(MagicPropertyFixture::class, 'magicName'); + $magicName = DocblockParser::parseProperty(MagicPropertyFixture::class, 'magicName'); expect((string) $magicName)->toBe('non-empty-string'); - $magicTags = ContractParser::parseProperty(MagicPropertyFixture::class, 'magicTags'); + $magicTags = DocblockParser::parseProperty(MagicPropertyFixture::class, 'magicTags'); expect((string) $magicTags)->toBe('list'); }); test('inherits magic @property docblocks across inheritance hierarchy', function () { - $inheritedRole = ContractParser::parseProperty(ChildMagicPropertyFixture::class, 'magicRole'); + $inheritedRole = DocblockParser::parseProperty(ChildMagicPropertyFixture::class, 'magicRole'); expect($inheritedRole)->not()->toBeNull() ->and((string) $inheritedRole)->toContain('admin') @@ -144,12 +144,12 @@ }); test('returns null for un-annotated properties or non-existent classes', function () { - expect(ContractParser::parseProperty('NonExistentClass123', 'prop'))->toBeNull(); - expect(ContractParser::parseProperty(ConfiguredProperty::class, 'nonExistentProperty'))->toBeNull(); + expect(DocblockParser::parseProperty('NonExistentClass123', 'prop'))->toBeNull(); + expect(DocblockParser::parseProperty(ConfiguredProperty::class, 'nonExistentProperty'))->toBeNull(); }); test('returns null for properties marked with @typephp-ignore', function () { - $ignored = ContractParser::parseProperty(IgnoredMethod::class, 'ignoredProperty'); + $ignored = DocblockParser::parseProperty(IgnoredMethod::class, 'ignoredProperty'); expect($ignored)->toBeNull(); }); @@ -157,7 +157,7 @@ describe('Magic Method Parsing (parseMagicMethod)', function () { test('parses dynamic @method annotations with variadics and optional parameters', function () { - $method = ContractParser::parseMagicMethod(MagicMethodFixture::class, 'processId'); + $method = DocblockParser::parseMagicMethod(MagicMethodFixture::class, 'processId'); expect($method)->not()->toBeNull() ->and((string) $method['return'])->toBe('positive-int') @@ -166,30 +166,30 @@ ->and((string) $method['parameters'][0]['type'])->toBe('positive-int') ; - $variadicMethod = ContractParser::parseMagicMethod(MagicMethodFixture::class, 'fetchList'); + $variadicMethod = DocblockParser::parseMagicMethod(MagicMethodFixture::class, 'fetchList'); expect($variadicMethod['parameters'][0]['isVariadic'])->toBeTrue(); }); test('inherits magic @method annotations from parent classes, interfaces, and traits', function () { - $parentMethod = ContractParser::parseMagicMethod(ChildMagicMethodService::class, 'parentMethod'); + $parentMethod = DocblockParser::parseMagicMethod(ChildMagicMethodService::class, 'parentMethod'); expect($parentMethod)->not()->toBeNull(); - $interfaceMethod = ContractParser::parseMagicMethod(ChildMagicMethodService::class, 'interfaceMethod'); + $interfaceMethod = DocblockParser::parseMagicMethod(ChildMagicMethodService::class, 'interfaceMethod'); expect($interfaceMethod)->not()->toBeNull(); - $traitMethod = ContractParser::parseMagicMethod(ChildMagicMethodService::class, 'traitMethod'); + $traitMethod = DocblockParser::parseMagicMethod(ChildMagicMethodService::class, 'traitMethod'); expect($traitMethod)->not()->toBeNull(); }); test('returns null for non-existent magic methods or non-existent classes', function () { - expect(ContractParser::parseMagicMethod('NonExistentClass123', 'method'))->toBeNull(); - expect(ContractParser::parseMagicMethod(MagicMethodFixture::class, 'nonExistentMagicMethod'))->toBeNull(); + expect(DocblockParser::parseMagicMethod('NonExistentClass123', 'method'))->toBeNull(); + expect(DocblockParser::parseMagicMethod(MagicMethodFixture::class, 'nonExistentMagicMethod'))->toBeNull(); }); }); describe('Class Aliases (parseClassAliases)', function () { test('parses and returns all local type aliases for a class', function () { - $aliases = ContractParser::parseClassAliases(NestedAliasService::class); + $aliases = DocblockParser::parseClassAliases(NestedAliasService::class); expect($aliases)->toHaveKey('LocalId') ->and($aliases)->toHaveKey('LocalStatus') @@ -199,7 +199,7 @@ }); test('returns empty array for non-existent classes', function () { - expect(ContractParser::parseClassAliases('NonExistentClass123'))->toBe([]); + expect(DocblockParser::parseClassAliases('NonExistentClass123'))->toBe([]); }); }); @@ -214,12 +214,12 @@ test('substitutes aliases in IdentifierTypeNode', function () { $node = new IdentifierTypeNode('UserId'); - $result = ContractParser::substituteAliases($node, $this->aliases); + $result = DocblockParser::substituteAliases($node, $this->aliases); expect((string) $result)->toBe('positive-int'); $unaliased = new IdentifierTypeNode('string'); - expect(ContractParser::substituteAliases($unaliased, $this->aliases))->toBe($unaliased); + expect(DocblockParser::substituteAliases($unaliased, $this->aliases))->toBe($unaliased); }); test('substitutes parameter and return aliases in CallableTypeNode', function () { @@ -230,7 +230,7 @@ [] ); - $result = ContractParser::substituteAliases($callableNode, $this->aliases); + $result = DocblockParser::substituteAliases($callableNode, $this->aliases); expect($result)->toBeInstanceOf(CallableTypeNode::class) ->and((string) $result)->toContain('positive-int') @@ -240,7 +240,7 @@ test('substitutes target and offset aliases in OffsetAccessTypeNode', function () { $offsetNode = new OffsetAccessTypeNode(new IdentifierTypeNode('UserId'), new IdentifierTypeNode('UserName')); - $result = ContractParser::substituteAliases($offsetNode, $this->aliases); + $result = DocblockParser::substituteAliases($offsetNode, $this->aliases); expect($result)->toBeInstanceOf(OffsetAccessTypeNode::class) ->and((string) $result->type)->toBe('positive-int') @@ -250,7 +250,7 @@ test('substitutes inner type aliases in ArrayTypeNode (UserId[] -> positive-int[])', function () { $arrNode = new ArrayTypeNode(new IdentifierTypeNode('UserId')); - $result = ContractParser::substituteAliases($arrNode, $this->aliases); + $result = DocblockParser::substituteAliases($arrNode, $this->aliases); expect($result)->toBeInstanceOf(ArrayTypeNode::class) ->and((string) $result)->toBe('positive-int[]') @@ -259,7 +259,7 @@ test('substitutes base and generic argument aliases in GenericTypeNode (Collection)', function () { $genericNode = new GenericTypeNode(new IdentifierTypeNode('Collection'), [new IdentifierTypeNode('UserId')]); - $result = ContractParser::substituteAliases($genericNode, $this->aliases); + $result = DocblockParser::substituteAliases($genericNode, $this->aliases); expect($result)->toBeInstanceOf(GenericTypeNode::class) ->and((string) $result)->toContain('positive-int') @@ -268,7 +268,7 @@ test('substitutes inner type aliases in NullableTypeNode (?UserId -> ?positive-int)', function () { $nullableNode = new NullableTypeNode(new IdentifierTypeNode('UserId')); - $result = ContractParser::substituteAliases($nullableNode, $this->aliases); + $result = DocblockParser::substituteAliases($nullableNode, $this->aliases); expect($result)->toBeInstanceOf(NullableTypeNode::class) ->and((string) $result)->toBe('?positive-int') @@ -277,7 +277,7 @@ test('substitutes member type aliases in UnionTypeNode (UserId|UserName)', function () { $unionNode = new UnionTypeNode([new IdentifierTypeNode('UserId'), new IdentifierTypeNode('UserName')]); - $result = ContractParser::substituteAliases($unionNode, $this->aliases); + $result = DocblockParser::substituteAliases($unionNode, $this->aliases); expect($result)->toBeInstanceOf(UnionTypeNode::class) ->and((string) $result)->toContain('positive-int') @@ -287,7 +287,7 @@ test('substitutes member type aliases in IntersectionTypeNode (UserId&UserName)', function () { $intersectionNode = new IntersectionTypeNode([new IdentifierTypeNode('UserId'), new IdentifierTypeNode('UserName')]); - $result = ContractParser::substituteAliases($intersectionNode, $this->aliases); + $result = DocblockParser::substituteAliases($intersectionNode, $this->aliases); expect($result)->toBeInstanceOf(IntersectionTypeNode::class) ->and((string) $result)->toContain('positive-int') @@ -301,7 +301,7 @@ new ArrayShapeItemNode(new ConstExprStringNode('id', ConstExprStringNode::SINGLE_QUOTED), false, new IdentifierTypeNode('UserId')), ], $unsealed); - $result = ContractParser::substituteAliases($shapeNode, $this->aliases); + $result = DocblockParser::substituteAliases($shapeNode, $this->aliases); expect($result)->toBeInstanceOf(ArrayShapeNode::class) ->and((string) $result->items[0]->valueType)->toBe('positive-int') @@ -316,7 +316,7 @@ new ObjectShapeItemNode(new IdentifierTypeNode('name'), false, new IdentifierTypeNode('UserName')), ]); - $result = ContractParser::substituteAliases($objShapeNode, $this->aliases); + $result = DocblockParser::substituteAliases($objShapeNode, $this->aliases); expect($result)->toBeInstanceOf(ObjectShapeNode::class) ->and((string) $result->items[0]->valueType)->toBe('positive-int') diff --git a/tests/Internal/DocblockNormalizerTest.php b/tests/Internal/Docblock/DocblockNormalizerTest.php similarity index 99% rename from tests/Internal/DocblockNormalizerTest.php rename to tests/Internal/Docblock/DocblockNormalizerTest.php index 9005cd7..650201d 100644 --- a/tests/Internal/DocblockNormalizerTest.php +++ b/tests/Internal/Docblock/DocblockNormalizerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\DocblockNormalizer; +use TypePHP\Internal\Docblock\DocblockNormalizer; describe('DocblockNormalizer', function () { test('returns docblock string unchanged when no special keywords or curly braces are present', function () { diff --git a/tests/Resolver/TemplateManagerTest.php b/tests/Internal/Generics/TemplateManagerTest.php similarity index 99% rename from tests/Resolver/TemplateManagerTest.php rename to tests/Internal/Generics/TemplateManagerTest.php index 3f876c1..b4febba 100644 --- a/tests/Resolver/TemplateManagerTest.php +++ b/tests/Internal/Generics/TemplateManagerTest.php @@ -6,8 +6,8 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Generics\TemplateManager; use TypePHP\Tests\Fixtures\Domain\Animal; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Cat; diff --git a/tests/Resolver/TemplateSubstitutorTest.php b/tests/Internal/Generics/TemplateSubstitutorTest.php similarity index 99% rename from tests/Resolver/TemplateSubstitutorTest.php rename to tests/Internal/Generics/TemplateSubstitutorTest.php index 021b28e..39002df 100644 --- a/tests/Resolver/TemplateSubstitutorTest.php +++ b/tests/Internal/Generics/TemplateSubstitutorTest.php @@ -19,7 +19,7 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Resolver\TemplateSubstitutor; +use TypePHP\Internal\Generics\TemplateSubstitutor; describe('TemplateSubstitutor Unit Tests', function () { test('substitutes simple identifier template placeholders', function () { diff --git a/tests/Internal/CacheManagerTest.php b/tests/Internal/Io/CacheManagerTest.php similarity index 95% rename from tests/Internal/CacheManagerTest.php rename to tests/Internal/Io/CacheManagerTest.php index b216c6d..5ce01a6 100644 --- a/tests/Internal/CacheManagerTest.php +++ b/tests/Internal/Io/CacheManagerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\CacheManager; +use TypePHP\Internal\Io\CacheManager; describe('CacheManager Unit Tests', function () { test('returns valid cache directory path', function () { diff --git a/tests/Internal/StreamWrapperTest.php b/tests/Internal/Io/StreamWrapperTest.php similarity index 98% rename from tests/Internal/StreamWrapperTest.php rename to tests/Internal/Io/StreamWrapperTest.php index 664d80d..2140b33 100644 --- a/tests/Internal/StreamWrapperTest.php +++ b/tests/Internal/Io/StreamWrapperTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use TypePHP\Contract\FileFilter; -use TypePHP\Internal\Config; -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\FileFilter; describe('StreamWrapper Unit Tests', function () { beforeEach(function () { @@ -302,7 +302,7 @@ function testIgnoredFileFunc(int $id): int test('bypasses AST transformation on non-PHP files', function () { $wrapper = new StreamWrapper(); $openedPath = null; - $jsonFile = __DIR__ . '/../../composer.json'; + $jsonFile = \dirname(__DIR__, 3) . '/composer.json'; $success = $wrapper->stream_open($jsonFile, 'r', 0, $openedPath); expect($success)->toBeTrue(); @@ -407,6 +407,11 @@ function testIgnoredFileFunc(int $id): int }); describe('File Functions Non-Interference (STREAM_OPEN_FOR_INCLUDE)', function () { + afterEach(function () { + Config::reset(); + StreamWrapper::reset(); + }); + test('file_get_contents() returns raw source code without AST transformation', function () { StreamWrapper::register(); @@ -476,6 +481,7 @@ function sampleAction(int $id): string if (is_dir($tempDir)) { @rmdir($tempDir); } + Config::reset(); } }); @@ -567,6 +573,7 @@ function dedicatedStreamAction(int $id): int if (is_dir($tempDir)) { @rmdir($tempDir); } + Config::reset(); } }); }); diff --git a/tests/Resolver/SpecialTypeResolverTest.php b/tests/Internal/Resolver/SpecialTypeResolverTest.php similarity index 99% rename from tests/Resolver/SpecialTypeResolverTest.php rename to tests/Internal/Resolver/SpecialTypeResolverTest.php index 3026af0..3da440b 100644 --- a/tests/Resolver/SpecialTypeResolverTest.php +++ b/tests/Internal/Resolver/SpecialTypeResolverTest.php @@ -20,8 +20,8 @@ use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; -use TypePHP\Internal\ErrorMessage; -use TypePHP\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Resolver\SpecialTypeResolver; use TypePHP\Tests\Fixtures\Generics\InlineTraitUseService; use TypePHP\Tests\Fixtures\Services\BaseService; use TypePHP\Tests\Fixtures\Services\UserService; diff --git a/tests/Internal/RuntimeTypeCheckerTest.php b/tests/Internal/RuntimeTypeCheckerTest.php index 289395f..22b7ac8 100644 --- a/tests/Internal/RuntimeTypeCheckerTest.php +++ b/tests/Internal/RuntimeTypeCheckerTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use TypePHP\Internal\Config; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; use TypePHP\Internal\RuntimeTypeChecker; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Types\ConfiguredProperty; describe('RuntimeTypeChecker Unit Tests', function () { diff --git a/tests/Internal/ClassNameValidatorTest.php b/tests/Internal/Util/ClassNameValidatorTest.php similarity index 97% rename from tests/Internal/ClassNameValidatorTest.php rename to tests/Internal/Util/ClassNameValidatorTest.php index f71070a..b79e6cf 100644 --- a/tests/Internal/ClassNameValidatorTest.php +++ b/tests/Internal/Util/ClassNameValidatorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\ClassNameValidator; +use TypePHP\Internal\Util\ClassNameValidator; describe('ClassNameValidator', function () { test('accepts valid simple and namespaced PHP class names', function () { diff --git a/tests/Internal/ConfigTest.php b/tests/Internal/Util/ConfigTest.php similarity index 99% rename from tests/Internal/ConfigTest.php rename to tests/Internal/Util/ConfigTest.php index 1c0439c..f2308dd 100644 --- a/tests/Internal/ConfigTest.php +++ b/tests/Internal/Util/ConfigTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; describe('Config Unit Tests', function () { afterEach(function () { diff --git a/tests/Internal/PathMatcherTest.php b/tests/Internal/Util/PathMatcherTest.php similarity index 94% rename from tests/Internal/PathMatcherTest.php rename to tests/Internal/Util/PathMatcherTest.php index cfd4e82..ab49b93 100644 --- a/tests/Internal/PathMatcherTest.php +++ b/tests/Internal/Util/PathMatcherTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use TypePHP\Internal\CacheManager; -use TypePHP\Internal\Config; -use TypePHP\Internal\PathMatcher; +use TypePHP\Internal\Io\CacheManager; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\PathMatcher; describe('PathMatcher Unit Tests', function () { beforeEach(function () { @@ -112,12 +112,15 @@ $projectRoot = PathMatcher::normalizePath(Config::getProjectRoot()); $internalFile = $projectRoot . '/src/Internal/RuntimeTypeChecker.php'; - $contractFile = $projectRoot . '/src/Contract/FileFilter.php'; - $commandFile = $projectRoot . '/src/Command/RunCommand.php'; - $validatorFile = $projectRoot . '/src/Validator/ArrayValidator.php'; - $wrapperFile = $projectRoot . '/src/Wrapper/CallableWrapper.php'; - $resolverFile = $projectRoot . '/src/Resolver/SpecialTypeResolver.php'; - $extensionFile = $projectRoot . '/src/Extension/ExtensionManager.php'; + $utilFile = $projectRoot . '/src/Internal/Util/FileFilter.php'; + $cliFile = $projectRoot . '/src/Internal/Cli/RunCommand.php'; + $validatorFile = $projectRoot . '/src/Internal/Validator/ArrayValidator.php'; + $wrapperFile = $projectRoot . '/src/Internal/Wrapper/CallableWrapper.php'; + $resolverFile = $projectRoot . '/src/Internal/Resolver/SpecialTypeResolver.php'; + $docblockFile = $projectRoot . '/src/Internal/Docblock/DocblockExtractor.php'; + $genericsFile = $projectRoot . '/src/Internal/Generics/TemplateManager.php'; + $ioFile = $projectRoot . '/src/Internal/Io/StreamWrapper.php'; + $extensionFile = $projectRoot . '/src/Extension/ExtensionInterface.php'; $exceptionFile = $projectRoot . '/src/Exception/TypeError.php'; $entryFile = $projectRoot . '/src/TypePHP.php'; $bootstrapFile = $projectRoot . '/src/bootstrap.php'; @@ -125,11 +128,14 @@ $externalFile = '/var/www/app/Models/User.php'; expect(PathMatcher::isLibraryInternal($internalFile))->toBeTrue() - ->and(PathMatcher::isLibraryInternal($contractFile))->toBeTrue() - ->and(PathMatcher::isLibraryInternal($commandFile))->toBeTrue() + ->and(PathMatcher::isLibraryInternal($utilFile))->toBeTrue() + ->and(PathMatcher::isLibraryInternal($cliFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($validatorFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($wrapperFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($resolverFile))->toBeTrue() + ->and(PathMatcher::isLibraryInternal($docblockFile))->toBeTrue() + ->and(PathMatcher::isLibraryInternal($genericsFile))->toBeTrue() + ->and(PathMatcher::isLibraryInternal($ioFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($extensionFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($exceptionFile))->toBeTrue() ->and(PathMatcher::isLibraryInternal($entryFile))->toBeTrue() diff --git a/tests/Internal/StubManagerTest.php b/tests/Internal/Util/StubManagerTest.php similarity index 97% rename from tests/Internal/StubManagerTest.php rename to tests/Internal/Util/StubManagerTest.php index af66c02..c4d82d5 100644 --- a/tests/Internal/StubManagerTest.php +++ b/tests/Internal/Util/StubManagerTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use TypePHP\Internal\Config; -use TypePHP\Internal\StubManager; +use TypePHP\Internal\Util\Config; +use TypePHP\Internal\Util\StubManager; describe('StubManager Unit Tests', function () { beforeEach(function () { diff --git a/tests/Wrapper/CallableWrapperTest.php b/tests/Internal/Wrapper/CallableWrapperTest.php similarity index 95% rename from tests/Wrapper/CallableWrapperTest.php rename to tests/Internal/Wrapper/CallableWrapperTest.php index 8cdbe61..2f3d58e 100644 --- a/tests/Wrapper/CallableWrapperTest.php +++ b/tests/Internal/Wrapper/CallableWrapperTest.php @@ -6,8 +6,8 @@ use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use TypePHP\Exception\TypeError; -use TypePHP\Validator\TypeValidatorRegistry; -use TypePHP\Wrapper\CallableWrapper; +use TypePHP\Internal\Validator\TypeValidatorRegistry; +use TypePHP\Internal\Wrapper\CallableWrapper; describe('CallableWrapper Unit Tests', function () { test('returns raw value if callable is not valid or node is not CallableTypeNode', function () { diff --git a/tests/Wrapper/IteratorProxyTest.php b/tests/Internal/Wrapper/IteratorProxyTest.php similarity index 97% rename from tests/Wrapper/IteratorProxyTest.php rename to tests/Internal/Wrapper/IteratorProxyTest.php index 397964f..bd00311 100644 --- a/tests/Wrapper/IteratorProxyTest.php +++ b/tests/Internal/Wrapper/IteratorProxyTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Wrapper\IteratorProxy; +use TypePHP\Internal\Wrapper\IteratorProxy; describe('IteratorProxy Unit Tests', function () { test('iterates inner traversable cleanly while executing type check callback', function () { diff --git a/tests/TypeChecking/ArraysAndShapes/AdvancedTypesAndEnumsTest.php b/tests/TypeChecking/ArraysAndShapes/AdvancedTypesAndEnumsTest.php index 5c4beab..15fd4d3 100644 --- a/tests/TypeChecking/ArraysAndShapes/AdvancedTypesAndEnumsTest.php +++ b/tests/TypeChecking/ArraysAndShapes/AdvancedTypesAndEnumsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Types\ConfigApp; use TypePHP\Tests\Fixtures\Types\StatusEnum; use TypePHP\Tests\Fixtures\Types\UserObjectShape; diff --git a/tests/TypeChecking/ArraysAndShapes/ArrayComplexSubtypesTest.php b/tests/TypeChecking/ArraysAndShapes/ArrayComplexSubtypesTest.php index 2e1a211..5771cec 100644 --- a/tests/TypeChecking/ArraysAndShapes/ArrayComplexSubtypesTest.php +++ b/tests/TypeChecking/ArraysAndShapes/ArrayComplexSubtypesTest.php @@ -3,7 +3,6 @@ declare(strict_types=1); use TypePHP\Exception\TypeError; -use TypePHP\Internal\Config; use TypePHP\Tests\Fixtures\Domain\Animal; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Cat; @@ -16,35 +15,99 @@ class ArraySubtypeBird extends Animal class ArrayTripleInterfaceObject implements Countable, ArrayAccess, Iterator { private array $data = ['a' => 1]; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } - public function rewind(): void { reset($this->data); } - public function current(): mixed { return current($this->data); } - public function key(): mixed { return key($this->data); } - public function next(): void { next($this->data); } - public function valid(): bool { return key($this->data) !== null; } + + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } + + public function rewind(): void + { + reset($this->data); + } + + public function current(): mixed + { + return current($this->data); + } + + public function key(): mixed + { + return key($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } } class ArrayDoubleInterfaceObject implements Countable, ArrayAccess { private array $data = ['a' => 1]; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } + + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } } class ArrayCountableOnly implements Countable { - public function count(): int { return 1; } + public function count(): int + { + return 1; + } } /** * @param list $animals + * * @return list */ function acceptSupersetAnimalList(array $animals): array @@ -54,6 +117,7 @@ function acceptSupersetAnimalList(array $animals): array /** * @param list $animals + * * @return list */ function acceptSubsetAnimalList(array $animals): array @@ -161,14 +225,16 @@ function acceptNestedAnimalList(array $nested): array $broadArray = [new Dog(), new Cat(), new ArraySubtypeBird()]; expect(fn () => acceptSubsetAnimalList($broadArray)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); test('strictly rejects array containing incompatible element not in union', function () { $incompatibleArray = [new Dog(), new Car()]; expect(fn () => acceptSupersetAnimalList($incompatibleArray)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); @@ -192,7 +258,8 @@ function acceptNestedAnimalList(array $nested): array $incompleteList = [new ArrayCountableOnly()]; expect(fn () => acceptIntersectionList($incompleteList)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); @@ -206,7 +273,7 @@ function acceptNestedAnimalList(array $nested): array test('allows array with heterogeneous items satisfying different branches of DNF', function () { $mixedDnfList = [ new ArrayDoubleInterfaceObject(), - new ArrayTripleInterfaceObject(), + new ArrayTripleInterfaceObject(), ]; expect(acceptDnfList($mixedDnfList))->toBe($mixedDnfList); @@ -215,11 +282,12 @@ function acceptNestedAnimalList(array $nested): array test('strictly rejects array containing an object that fails all DNF branches', function () { $badList = [ new ArrayDoubleInterfaceObject(), - new ArrayCountableOnly(), + new ArrayCountableOnly(), ]; expect(fn () => acceptDnfList($badList)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); @@ -240,7 +308,8 @@ function acceptNestedAnimalList(array $nested): array ]; expect(fn () => acceptNestedAnimalList($nestedBad)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Boundaries/CrlfLineDriftTest.php b/tests/TypeChecking/Boundaries/CrlfLineDriftTest.php index 541ceb2..ee667fc 100644 --- a/tests/TypeChecking/Boundaries/CrlfLineDriftTest.php +++ b/tests/TypeChecking/Boundaries/CrlfLineDriftTest.php @@ -6,7 +6,7 @@ return; } -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; describe('CRLF (\r\n) Windows Line-Drift Stress Test', function () { test('transforms CRLF (\r\n) functions with zero line-drift', function () { diff --git a/tests/TypeChecking/Boundaries/InlineReturnValidationTest.php b/tests/TypeChecking/Boundaries/InlineReturnValidationTest.php index cfad65e..cd4e83e 100644 --- a/tests/TypeChecking/Boundaries/InlineReturnValidationTest.php +++ b/tests/TypeChecking/Boundaries/InlineReturnValidationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; /** * Function with broad return type, but specific inline @var on return statement diff --git a/tests/TypeChecking/Boundaries/InlineVariableValidationTest.php b/tests/TypeChecking/Boundaries/InlineVariableValidationTest.php index 40bcaf3..35d1f24 100644 --- a/tests/TypeChecking/Boundaries/InlineVariableValidationTest.php +++ b/tests/TypeChecking/Boundaries/InlineVariableValidationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Cat; use TypePHP\Tests\Fixtures\Domain\Dog; diff --git a/tests/TypeChecking/Boundaries/MagicMethodsTest.php b/tests/TypeChecking/Boundaries/MagicMethodsTest.php index 8833830..ea573a3 100644 --- a/tests/TypeChecking/Boundaries/MagicMethodsTest.php +++ b/tests/TypeChecking/Boundaries/MagicMethodsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\Producer; diff --git a/tests/TypeChecking/Boundaries/MagicPropertiesTest.php b/tests/TypeChecking/Boundaries/MagicPropertiesTest.php index ed15793..534a62b 100644 --- a/tests/TypeChecking/Boundaries/MagicPropertiesTest.php +++ b/tests/TypeChecking/Boundaries/MagicPropertiesTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\Producer; diff --git a/tests/TypeChecking/Boundaries/NormalUnionAndIntersectionSubtypesTest.php b/tests/TypeChecking/Boundaries/NormalUnionAndIntersectionSubtypesTest.php index f632927..6f95438 100644 --- a/tests/TypeChecking/Boundaries/NormalUnionAndIntersectionSubtypesTest.php +++ b/tests/TypeChecking/Boundaries/NormalUnionAndIntersectionSubtypesTest.php @@ -18,17 +18,60 @@ class NormalQuadObject implements Countable, ArrayAccess, Iterator, Stringable { private array $data = ['key' => 'value']; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } - public function rewind(): void { reset($this->data); } - public function current(): mixed { return current($this->data); } - public function key(): mixed { return key($this->data); } - public function next(): void { next($this->data); } - public function valid(): bool { return key($this->data) !== null; } - public function __toString(): string { return 'quad_object'; } + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } + + public function rewind(): void + { + reset($this->data); + } + + public function current(): mixed + { + return current($this->data); + } + + public function key(): mixed + { + return key($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } + + public function __toString(): string + { + return 'quad_object'; + } } /** @@ -38,11 +81,30 @@ class NormalBranchAObject implements Countable, ArrayAccess { private array $data = ['a' => 1]; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } } /** @@ -52,12 +114,35 @@ class NormalBranchBObject implements Iterator, Stringable { private array $data = ['b' => 2]; - public function rewind(): void { reset($this->data); } - public function current(): mixed { return current($this->data); } - public function key(): mixed { return key($this->data); } - public function next(): void { next($this->data); } - public function valid(): bool { return key($this->data) !== null; } - public function __toString(): string { return 'branch_b_object'; } + public function rewind(): void + { + reset($this->data); + } + + public function current(): mixed + { + return current($this->data); + } + + public function key(): mixed + { + return key($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } + + public function __toString(): string + { + return 'branch_b_object'; + } } /** @@ -67,13 +152,23 @@ public function __toString(): string { return 'branch_b_object'; } */ class NormalCrossOverObject implements Countable, Stringable { - public function count(): int { return 1; } - public function __toString(): string { return 'crossover_object'; } + public function count(): int + { + return 1; + } + + public function __toString(): string + { + return 'crossover_object'; + } } class NormalSingleCountable implements Countable { - public function count(): int { return 1; } + public function count(): int + { + return 1; + } } /** @@ -81,11 +176,15 @@ public function count(): int { return 1; } */ class NormalSpecialDog extends Dog implements Countable { - public function count(): int { return 5; } + public function count(): int + { + return 5; + } } /** * @param Dog|Cat|NormalBird $animal + * * @return Dog|Cat|NormalBird */ function processNormalUnionAnimal(Animal $animal): Animal @@ -95,6 +194,7 @@ function processNormalUnionAnimal(Animal $animal): Animal /** * @param 'admin'|'editor'|'viewer'|'guest' $role + * * @return 'admin'|'editor'|'viewer'|'guest' */ function processNormalUnionLiteralRole(string $role): string @@ -104,6 +204,7 @@ function processNormalUnionLiteralRole(string $role): string /** * @param 1|2|3|4|5 $number + * * @return 1|2|3|4|5 */ function processNormalUnionLiteralNumber(int $number): int @@ -113,6 +214,7 @@ function processNormalUnionLiteralNumber(int $number): int /** * @param positive-int|non-empty-string $idOrCode + * * @return positive-int|non-empty-string */ function processNormalUnionRefinements(mixed $idOrCode): mixed @@ -122,6 +224,7 @@ function processNormalUnionRefinements(mixed $idOrCode): mixed /** * @param Countable&ArrayAccess $collection + * * @return Countable&ArrayAccess */ function processNormalIntersection(object $collection): object @@ -131,6 +234,7 @@ function processNormalIntersection(object $collection): object /** * @param Dog&Countable $pet + * * @return Dog&Countable */ function processNormalClassInterfaceIntersection(object $pet): object @@ -142,6 +246,7 @@ function processNormalClassInterfaceIntersection(object $pet): object * DNF: (Countable & ArrayAccess) | (Iterator & Stringable) * * @param (Countable&ArrayAccess)|(Iterator&Stringable) $dnf + * * @return (Countable&ArrayAccess)|(Iterator&Stringable) */ function processNormalDnf(object $dnf): object @@ -208,19 +313,24 @@ function processNormalDnf(object $dnf): object test('strictly rejects value not present in union', function () { expect(fn () => processNormalUnionAnimal(new Car())) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; expect(fn () => processNormalUnionLiteralRole('superadmin')) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; expect(fn () => processNormalUnionLiteralNumber(99)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; expect(fn () => processNormalUnionRefinements(-50)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; expect(fn () => processNormalUnionRefinements('')) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); @@ -248,15 +358,18 @@ function processNormalDnf(object $dnf): object test('strictly rejects object missing one required interface of the intersection', function () { expect(fn () => processNormalIntersection(new NormalSingleCountable())) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); test('strictly rejects object failing class or interface part of class-interface intersection', function () { expect(fn () => processNormalClassInterfaceIntersection(new Dog())) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; expect(fn () => processNormalClassInterfaceIntersection(new NormalSingleCountable())) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); @@ -292,19 +405,22 @@ function processNormalDnf(object $dnf): object $crossOver = new NormalCrossOverObject(); expect(fn () => processNormalDnf($crossOver)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); test('strictly rejects object satisfying only a single interface of one branch', function () { $single = new NormalSingleCountable(); expect(fn () => processNormalDnf($single)) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); test('strictly rejects completely unrelated object', function () { expect(fn () => processNormalDnf(new Car())) - ->toThrow(TypeError::class); + ->toThrow(TypeError::class) + ; }); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Boundaries/ParamContractsTest.php b/tests/TypeChecking/Boundaries/ParamContractsTest.php index d7b428c..7aa25ef 100644 --- a/tests/TypeChecking/Boundaries/ParamContractsTest.php +++ b/tests/TypeChecking/Boundaries/ParamContractsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Contract\ContractParser; +use TypePHP\Internal\Docblock\DocblockParser; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Services\VariadicPropertyService; @@ -114,7 +114,7 @@ function testPureMixedParamFunction(mixed $data, mixed $meta): bool }); test('filters out pure mixed parameters so hasParamContract is false', function () { - $contract = ContractParser::parse('testPureMixedParamFunction'); + $contract = DocblockParser::parse('testPureMixedParamFunction'); expect($contract['types'])->toBeEmpty() ->and($contract['hasParamContract'])->toBeFalse() diff --git a/tests/TypeChecking/Boundaries/PipeOperatorTest.php b/tests/TypeChecking/Boundaries/PipeOperatorTest.php index 0db7638..5a55db3 100644 --- a/tests/TypeChecking/Boundaries/PipeOperatorTest.php +++ b/tests/TypeChecking/Boundaries/PipeOperatorTest.php @@ -6,7 +6,7 @@ return; } -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; use TypePHP\Tests\Fixtures\Pipes\NativePipeRunner; use TypePHP\Tests\Fixtures\Pipes\PipePipelineService; diff --git a/tests/TypeChecking/Boundaries/PropertyHooksTest.php b/tests/TypeChecking/Boundaries/PropertyHooksTest.php index 064379a..35d35bc 100644 --- a/tests/TypeChecking/Boundaries/PropertyHooksTest.php +++ b/tests/TypeChecking/Boundaries/PropertyHooksTest.php @@ -6,7 +6,7 @@ return; } -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Types\HookedInterfaceImplementation; use TypePHP\Tests\Fixtures\Types\HookedUser; use TypePHP\Tests\Fixtures\Types\PropertyHooks; diff --git a/tests/TypeChecking/Boundaries/PropertyValidationTest.php b/tests/TypeChecking/Boundaries/PropertyValidationTest.php index caa42fb..487c7fd 100644 --- a/tests/TypeChecking/Boundaries/PropertyValidationTest.php +++ b/tests/TypeChecking/Boundaries/PropertyValidationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\Producer; diff --git a/tests/TypeChecking/Boundaries/ShopwareSyntaxBugTest.php b/tests/TypeChecking/Boundaries/ShopwareSyntaxBugTest.php index bfc52c7..e7f9032 100644 --- a/tests/TypeChecking/Boundaries/ShopwareSyntaxBugTest.php +++ b/tests/TypeChecking/Boundaries/ShopwareSyntaxBugTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Shopware\Config\MetricConfigProvider; beforeEach(function () { diff --git a/tests/TypeChecking/Conditionals/ListSubtypeOfArrayConditionalTest.php b/tests/TypeChecking/Conditionals/ListSubtypeOfArrayConditionalTest.php index 0070d38..6999308 100644 --- a/tests/TypeChecking/Conditionals/ListSubtypeOfArrayConditionalTest.php +++ b/tests/TypeChecking/Conditionals/ListSubtypeOfArrayConditionalTest.php @@ -29,4 +29,4 @@ function testListIsArrayConditional(mixed $match, mixed $returnVal): mixed expect($result)->toBe(['key1' => 'val1', 'key2' => 'val2']); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Conditionals/MutatedParamConditionalReturnTest.php b/tests/TypeChecking/Conditionals/MutatedParamConditionalReturnTest.php index 6690540..6f85a20 100644 --- a/tests/TypeChecking/Conditionals/MutatedParamConditionalReturnTest.php +++ b/tests/TypeChecking/Conditionals/MutatedParamConditionalReturnTest.php @@ -39,4 +39,4 @@ function testMutatedParameterConditional(int $length): string expect($result)->toBe(''); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Conditionals/NullableTemplateConditionalReturnTest.php b/tests/TypeChecking/Conditionals/NullableTemplateConditionalReturnTest.php index a692a9f..e1dbd24 100644 --- a/tests/TypeChecking/Conditionals/NullableTemplateConditionalReturnTest.php +++ b/tests/TypeChecking/Conditionals/NullableTemplateConditionalReturnTest.php @@ -34,4 +34,4 @@ function testNullableTemplateConditional(?string $match = null, mixed $returnVal expect($result)->toBe('matched_id_string'); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Configuration/BoundaryConfigTest.php b/tests/TypeChecking/Configuration/BoundaryConfigTest.php index 1ba4a74..cafa98d 100644 --- a/tests/TypeChecking/Configuration/BoundaryConfigTest.php +++ b/tests/TypeChecking/Configuration/BoundaryConfigTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use TypePHP\Exception\TypeError; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\GenericCollection; diff --git a/tests/TypeChecking/Configuration/RespectIgnoreTagsConfigTest.php b/tests/TypeChecking/Configuration/RespectIgnoreTagsConfigTest.php index 9cbcad7..0b8bbfc 100644 --- a/tests/TypeChecking/Configuration/RespectIgnoreTagsConfigTest.php +++ b/tests/TypeChecking/Configuration/RespectIgnoreTagsConfigTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\IgnoreTags\ForceCheckedMethod; describe('Respect Ignore Tags Configuration (respect_ignore_tags)', function () { diff --git a/tests/TypeChecking/Configuration/RespectNativeNullabilityTemplateTest.php b/tests/TypeChecking/Configuration/RespectNativeNullabilityTemplateTest.php index 4d51612..99d72d6 100644 --- a/tests/TypeChecking/Configuration/RespectNativeNullabilityTemplateTest.php +++ b/tests/TypeChecking/Configuration/RespectNativeNullabilityTemplateTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Domain\Animal; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; diff --git a/tests/TypeChecking/Configuration/RespectNativeReturnNullabilityTest.php b/tests/TypeChecking/Configuration/RespectNativeReturnNullabilityTest.php index 4e4470c..48cf228 100644 --- a/tests/TypeChecking/Configuration/RespectNativeReturnNullabilityTest.php +++ b/tests/TypeChecking/Configuration/RespectNativeReturnNullabilityTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use TypePHP\Exception\TypeError; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; /** * Function with native : ?array return, but non-nullable DocBlock diff --git a/tests/TypeChecking/Generics/ClassTemplateInlineVarTest.php b/tests/TypeChecking/Generics/ClassTemplateInlineVarTest.php index 04a8744..627fbb0 100644 --- a/tests/TypeChecking/Generics/ClassTemplateInlineVarTest.php +++ b/tests/TypeChecking/Generics/ClassTemplateInlineVarTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use TypePHP\Exception\TypeError; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Collections\ShopwareCollection; use TypePHP\Tests\Fixtures\Collections\ShopwareEntityCollection; use TypePHP\Tests\Fixtures\Collections\ShopwareEntitySearchResult; diff --git a/tests/TypeChecking/Generics/ComplexUnionAndIntersectionSubtypesTest.php b/tests/TypeChecking/Generics/ComplexUnionAndIntersectionSubtypesTest.php index 2cac0e5..16270be 100644 --- a/tests/TypeChecking/Generics/ComplexUnionAndIntersectionSubtypesTest.php +++ b/tests/TypeChecking/Generics/ComplexUnionAndIntersectionSubtypesTest.php @@ -8,8 +8,12 @@ use TypePHP\Tests\Fixtures\Domain\Cat; use TypePHP\Tests\Fixtures\Domain\Dog; -class Bird extends Animal {} -class Fish extends Animal {} +class Bird extends Animal +{ +} +class Fish extends Animal +{ +} /** * Fixture representing an object that implements THREE interfaces @@ -17,26 +21,86 @@ class Fish extends Animal {} class TripleInterfaceObject implements Countable, ArrayAccess, Iterator { private array $data = ['a' => 1]; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } - public function rewind(): void { reset($this->data); } - public function current(): mixed { return current($this->data); } - public function key(): mixed { return key($this->data); } - public function next(): void { next($this->data); } - public function valid(): bool { return key($this->data) !== null; } + + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } + + public function rewind(): void + { + reset($this->data); + } + + public function current(): mixed + { + return current($this->data); + } + + public function key(): mixed + { + return key($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function valid(): bool + { + return key($this->data) !== null; + } } class DoubleInterfaceObject implements Countable, ArrayAccess { private array $data = ['a' => 1]; - public function count(): int { return count($this->data); } - public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); } - public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; } - public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; } - public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); } + + public function count(): int + { + return \count($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->data[$offset]); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->data[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->data[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->data[$offset]); + } } /** @@ -44,7 +108,9 @@ public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); */ class TypeSetHolder { - /** @var array */ + /** + * @var array + */ public array $items = []; } @@ -150,4 +216,4 @@ class TypeSetHolder })->toThrow(TypeError::class); }); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Generics/GenericTraitsUseAnnotationTest.php b/tests/TypeChecking/Generics/GenericTraitsUseAnnotationTest.php index 52dd0f3..4ca556f 100644 --- a/tests/TypeChecking/Generics/GenericTraitsUseAnnotationTest.php +++ b/tests/TypeChecking/Generics/GenericTraitsUseAnnotationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use TypePHP\Exception\TypeError; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Generics\TemplateManager; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Dog; use TypePHP\Tests\Fixtures\Generics\ClassLevelTraitService; diff --git a/tests/TypeChecking/Generics/GenericUnionSubsetVarianceTest.php b/tests/TypeChecking/Generics/GenericUnionSubsetVarianceTest.php index 465cecf..cb9a6e3 100644 --- a/tests/TypeChecking/Generics/GenericUnionSubsetVarianceTest.php +++ b/tests/TypeChecking/Generics/GenericUnionSubsetVarianceTest.php @@ -2,17 +2,27 @@ declare(strict_types=1); -class StmtA {} -class StmtB {} -class StmtC {} -class StmtUnrelated {} +class StmtA +{ +} +class StmtB +{ +} +class StmtC +{ +} +class StmtUnrelated +{ +} /** * @template T */ class GenericUnionHolderFixture { - /** @var array */ + /** + * @var array + */ public array $items = []; } @@ -39,6 +49,6 @@ class GenericUnionHolderFixture expect(function () use (&$container, $incompatibleContainer) { $container = $incompatibleContainer; - })->toThrow(\TypeError::class); + })->toThrow(TypeError::class); }); -}); \ No newline at end of file +}); diff --git a/tests/TypeChecking/Generics/UnspecializedArrayKeyGenericTest.php b/tests/TypeChecking/Generics/UnspecializedArrayKeyGenericTest.php index de00a90..9baf703 100644 --- a/tests/TypeChecking/Generics/UnspecializedArrayKeyGenericTest.php +++ b/tests/TypeChecking/Generics/UnspecializedArrayKeyGenericTest.php @@ -5,7 +5,7 @@ namespace TypePHP\Tests\TypeChecking\Generics; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Generics\TemplateManager; use TypePHP\TypePHP; /** diff --git a/tests/TypeChecking/Generics/UnspecializedGenericDefaultTest.php b/tests/TypeChecking/Generics/UnspecializedGenericDefaultTest.php index e192724..bd24233 100644 --- a/tests/TypeChecking/Generics/UnspecializedGenericDefaultTest.php +++ b/tests/TypeChecking/Generics/UnspecializedGenericDefaultTest.php @@ -4,7 +4,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use TypePHP\Exception\TypeError; -use TypePHP\Resolver\TemplateManager; +use TypePHP\Internal\Generics\TemplateManager; use TypePHP\TypePHP; interface SpecificAppMiddleware diff --git a/tests/TypeChecking/InheritanceAndAttributes/ConstructorPropertyTypeMismatchTest.php b/tests/TypeChecking/InheritanceAndAttributes/ConstructorPropertyTypeMismatchTest.php index a0a6d9f..71e1cb0 100644 --- a/tests/TypeChecking/InheritanceAndAttributes/ConstructorPropertyTypeMismatchTest.php +++ b/tests/TypeChecking/InheritanceAndAttributes/ConstructorPropertyTypeMismatchTest.php @@ -36,8 +36,8 @@ public function __construct(int $userId) ; }); - test('ContractParser::parse resolves property aliases for constructor parameters', function () { - $contract = TypePHP\Contract\ContractParser::parse(ConstructorPromotionWithAlias::class . '::__construct'); + test('DocblockParser::parse resolves property aliases for constructor parameters', function () { + $contract = TypePHP\Internal\Docblock\DocblockParser::parse(ConstructorPromotionWithAlias::class . '::__construct'); expect((string) $contract['types']['userId'])->toBe('positive-int'); }); diff --git a/tests/TypeChecking/InheritanceAndAttributes/LiskovAndVendorIsolationTest.php b/tests/TypeChecking/InheritanceAndAttributes/LiskovAndVendorIsolationTest.php index 3d8b8ec..735a1ca 100644 --- a/tests/TypeChecking/InheritanceAndAttributes/LiskovAndVendorIsolationTest.php +++ b/tests/TypeChecking/InheritanceAndAttributes/LiskovAndVendorIsolationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Liskov\AppChildService; use TypePHP\Tests\Fixtures\Liskov\ChildLiskovService; use TypePHP\Tests\Fixtures\Liskov\RenamedParamImplementation; diff --git a/tests/TypeChecking/InheritanceAndAttributes/ParameterShiftInheritanceTest.php b/tests/TypeChecking/InheritanceAndAttributes/ParameterShiftInheritanceTest.php index 6c45504..6e3b129 100644 --- a/tests/TypeChecking/InheritanceAndAttributes/ParameterShiftInheritanceTest.php +++ b/tests/TypeChecking/InheritanceAndAttributes/ParameterShiftInheritanceTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Services\ChildShiftedMethodService; use TypePHP\Tests\Fixtures\Services\ChildShiftedOopService; use TypePHP\Tests\Fixtures\Services\ChildShiftedParamService; diff --git a/tests/TypeChecking/InheritanceAndAttributes/SameNamespaceGlobalCollisionTest.php b/tests/TypeChecking/InheritanceAndAttributes/SameNamespaceGlobalCollisionTest.php index 79bd7d4..e8c008b 100644 --- a/tests/TypeChecking/InheritanceAndAttributes/SameNamespaceGlobalCollisionTest.php +++ b/tests/TypeChecking/InheritanceAndAttributes/SameNamespaceGlobalCollisionTest.php @@ -5,7 +5,7 @@ namespace TypePHP\Tests\TypeChecking\InheritanceAndAttributes; use ReflectionMethod; -use TypePHP\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; use TypePHP\Tests\Fixtures\Shopware\Error\Error; use TypePHP\Tests\Fixtures\Shopware\Error\ErrorCollection; use TypePHP\Tests\Fixtures\Shopware\Error\TestError; diff --git a/tests/TypeChecking/InheritanceAndAttributes/SubNamespaceResolutionTest.php b/tests/TypeChecking/InheritanceAndAttributes/SubNamespaceResolutionTest.php index 4dba052..e566a83 100644 --- a/tests/TypeChecking/InheritanceAndAttributes/SubNamespaceResolutionTest.php +++ b/tests/TypeChecking/InheritanceAndAttributes/SubNamespaceResolutionTest.php @@ -8,7 +8,7 @@ use ReflectionFunction; use TypePHP\Exception\TypeError; -use TypePHP\Resolver\SpecialTypeResolver; +use TypePHP\Internal\Resolver\SpecialTypeResolver; use TypePHP\Tests\Fixtures\Domain; /** diff --git a/tests/TypeChecking/Stubs/StubSystemTest.php b/tests/TypeChecking/Stubs/StubSystemTest.php index 753ee93..1b50415 100644 --- a/tests/TypeChecking/Stubs/StubSystemTest.php +++ b/tests/TypeChecking/Stubs/StubSystemTest.php @@ -4,7 +4,7 @@ use TypePHP\Exception\TypeError; use TypePHP\Extension\ExtensionInterface; -use TypePHP\Internal\Config; +use TypePHP\Internal\Util\Config; use TypePHP\Tests\Fixtures\Liskov\AppChildService; use TypePHP\Tests\Fixtures\Liskov\SimulatedVendorParent; use TypePHP\Tests\Fixtures\Services\ChildMagicMethodService; diff --git a/tests/Unit/LineNumberPreservationTest.php b/tests/Unit/LineNumberPreservationTest.php index 428360d..c55b77e 100644 --- a/tests/Unit/LineNumberPreservationTest.php +++ b/tests/Unit/LineNumberPreservationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use TypePHP\Internal\StreamWrapper; +use TypePHP\Internal\Io\StreamWrapper; describe('Line Number Preservation', function () { test('transforms code without shifting original line numbers for parameter checks', function () { @@ -157,4 +157,55 @@ function testTrailingCommentFunc(int $id): int ->and($transformed)->toContain('RuntimeTypeChecker::checkReturn') ; }); + + test('does not corrupt URLs containing double slashes in string literals', function () { + $source = <<<'PHP' +toContain("'https://example.test/path'") + ->and($transformed)->not()->toContain('https:/*') + ; + + $tokens = token_get_all($transformed); + expect($tokens)->toBeArray(); + }); + + test('does not corrupt strings containing hashtags in string literals', function () { + $source = <<<'PHP' +toContain("'#FF0000'") + ->and($transformed)->not()->toContain('/*') + ; + }); }); diff --git a/tests/Unit/ValidatorsTest.php b/tests/Unit/ValidatorsTest.php index a94c61b..2c1dd87 100644 --- a/tests/Unit/ValidatorsTest.php +++ b/tests/Unit/ValidatorsTest.php @@ -8,7 +8,8 @@ use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\PhpDocParser\ParserConfig; -use TypePHP\Internal\ErrorMessage; +use TypePHP\Internal\Diagnostic\ErrorMessage; +use TypePHP\Internal\Validator\TypeValidatorRegistry; use TypePHP\Tests\Fixtures\Domain\Car; use TypePHP\Tests\Fixtures\Domain\Cat; use TypePHP\Tests\Fixtures\Domain\Dog; @@ -24,7 +25,6 @@ use TypePHP\Tests\Fixtures\Types\StatusEnum; use TypePHP\Tests\Fixtures\Types\UserObjectShape; use TypePHP\Tests\Fixtures\Types\WildcardConstantFixture; -use TypePHP\Validator\TypeValidatorRegistry; beforeEach(function () { $this->registry = new TypeValidatorRegistry();