/** * @param string $file path to a file to parse * @return \PhpParser\Node[] */ public function parseFile(string $file) : array { if (!isset($this->cachedNodesByFile[$file])) { $this->cachedNodesByFile[$file] = $this->originalParser->parseFile($file); } return $this->cachedNodesByFile[$file]; }
public function isVariadic() : bool { $isNativelyVariadic = $this->reflection->isVariadic(); if (!$isNativelyVariadic && $this->reflection->getFileName() !== false) { $key = sprintf('variadic-function-%s-v2', $this->reflection->getName()); $cachedResult = $this->cache->load($key); if ($cachedResult === null) { $nodes = $this->parser->parseFile($this->reflection->getFileName()); $result = $this->callsFuncGetArgs($nodes); $this->cache->save($key, $result); return $result; } return $cachedResult; } return $isNativelyVariadic; }
private function processTraitUse(Node\Stmt\TraitUse $node, Scope $classScope, \Closure $nodeCallback) { foreach ($node->traits as $trait) { $traitName = (string) $trait; if (!$this->broker->hasClass($traitName)) { continue; } $traitReflection = $this->broker->getClass($traitName); $fileName = $traitReflection->getNativeReflection()->getFileName(); $parserNodes = $this->parser->parseFile($fileName); $classScope = $classScope->changeAnalysedContextFile(sprintf('%s (in context of %s)', $fileName, $classScope->getClass() !== null ? sprintf('class %s', $classScope->getClass()) : 'anonymous class')); $this->processNodes($parserNodes, new Scope($this->broker, $this->printer, $fileName), function (\PhpParser\Node $node) use($traitName, $classScope, $nodeCallback) { if ($node instanceof Node\Stmt\Trait_ && $traitName === (string) $node->namespacedName) { $this->processNodes($node->stmts, $classScope, $nodeCallback); } }); } }
public function isVariadic() : bool { $isNativelyVariadic = $this->reflection->isVariadic(); if (!$isNativelyVariadic && $this->declaringClass->getName() === 'ReflectionMethod' && $this->reflection->getName() === 'invoke') { return true; } if (!$isNativelyVariadic && $this->declaringClass->getNativeReflection()->getFileName() !== false) { $key = sprintf('variadic-method-%s-%s-v2', $this->declaringClass->getName(), $this->reflection->getName()); $cachedResult = $this->cache->load($key); if ($cachedResult === null) { $nodes = $this->parser->parseFile($this->declaringClass->getNativeReflection()->getFileName()); $result = $this->callsFuncGetArgs($nodes); $this->cache->save($key, $result); return $result; } return $cachedResult; } return $isNativelyVariadic; }
/** * @param string[] $files * @param \Closure|null $progressCallback * @return string[]|\PHPStan\Analyser\Error[] errors */ public function analyse(array $files, \Closure $progressCallback = null) : array { $errors = []; if ($this->bootstrapFile !== null) { if (!is_file($this->bootstrapFile)) { return [sprintf('Bootstrap file %s does not exist.', $this->bootstrapFile)]; } try { require_once $this->bootstrapFile; } catch (\Throwable $e) { return [$e->getMessage()]; } } foreach ($this->ignoreErrors as $ignoreError) { try { \Nette\Utils\Strings::match('', $ignoreError); } catch (\Nette\Utils\RegexpException $e) { $errors[] = $e->getMessage(); } } if (count($errors) > 0) { return $errors; } foreach ($files as $file) { try { if ($this->isExcludedFromAnalysing($file)) { if ($progressCallback !== null) { $progressCallback($file); } continue; } $fileErrors = []; $this->nodeScopeResolver->processNodes($this->parser->parseFile($file), new Scope($this->broker, $this->printer, $file), function (\PhpParser\Node $node, Scope $scope) use(&$fileErrors) { if ($node instanceof \PhpParser\Node\Stmt\Trait_) { return; } $classes = array_merge([get_class($node)], class_parents($node)); foreach ($this->registry->getRules($classes) as $rule) { $ruleErrors = $this->createErrors($node, $scope->getAnalysedContextFile(), $rule->processNode($node, $scope)); $fileErrors = array_merge($fileErrors, $ruleErrors); } }); if ($progressCallback !== null) { $progressCallback($file); } $errors = array_merge($errors, $fileErrors); } catch (\PhpParser\Error $e) { $errors[] = new Error($e->getMessage(), $file); } catch (\PHPStan\AnalysedCodeException $e) { $errors[] = new Error($e->getMessage(), $file); } catch (\Throwable $t) { $errors[] = new Error(sprintf('Internal error: %s', $t->getMessage()), $file); } } $unmatchedIgnoredErrors = $this->ignoreErrors; $errors = array_values(array_filter($errors, function (string $error) use(&$unmatchedIgnoredErrors) : bool { foreach ($this->ignoreErrors as $i => $ignore) { if (\Nette\Utils\Strings::match($error, $ignore) !== null) { unset($unmatchedIgnoredErrors[$i]); return false; } } return true; })); foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) { $errors[] = sprintf('Ignored error pattern %s was not matched in reported errors.', $unmatchedIgnoredError); } return $errors; }
private function createTypeMap(string $fileName) : array { $objectTypes = []; $typeMap = []; $processTypeString = function (string $typeString, string $className = null) use(&$typeMap, &$objectTypes) { if (isset($typeMap[$typeString])) { return; } $type = $this->getTypeFromTypeString($typeString, $className); if ($type instanceof ArrayType) { $nestedItemType = $type->getNestedItemType(); if ($nestedItemType->getItemType() instanceof ObjectType) { if ($nestedItemType->getItemType()->getClass() === $className) { $typeMap[$typeString] = ArrayType::createDeepArrayType(new NestedArrayItemType(new ObjectType($className, false), $nestedItemType->getDepth()), $type->isNullable()); } else { $objectTypes[] = ['type' => $nestedItemType->getItemType(), 'typeString' => $typeString, 'arrayType' => ['depth' => $nestedItemType->getDepth(), 'nullable' => $type->isNullable()]]; } } else { $typeMap[$typeString] = $type; } return; } if (!$type instanceof ObjectType) { $typeMap[$typeString] = $type; return; } elseif ($type->getClass() === $className) { $typeMap[$typeString] = $type; return; } $objectTypes[] = ['type' => $type, 'typeString' => $typeString]; }; $patterns = ['#@param\\s+' . self::TYPE_PATTERN . '\\s+\\$[a-zA-Z0-9_]+#', '#@var\\s+' . self::TYPE_PATTERN . '#', '#@var\\s+\\$[a-zA-Z0-9_]+\\s+' . self::TYPE_PATTERN . '#', '#@return\\s+' . self::TYPE_PATTERN . '#']; /** @var \PhpParser\Node\Stmt\ClassLike|null $lastClass */ $lastClass = null; $this->processNodes($this->parser->parseFile($fileName), function (\PhpParser\Node $node, string $className = null) use($processTypeString, $patterns, &$lastClass) { if ($node instanceof Node\Stmt\ClassLike) { $lastClass = $node; } if (!in_array(get_class($node), [Node\Stmt\Property::class, Node\Stmt\ClassMethod::class, Node\Expr\Assign::class], true)) { return; } $comment = CommentHelper::getDocComment($node); if ($comment === null) { return; } foreach ($patterns as $pattern) { preg_match_all($pattern, $comment, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $processTypeString($match[1], $className); } } }); if (count($objectTypes) === 0) { return $typeMap; } $fileString = file_get_contents($fileName); if ($lastClass !== null) { $classType = 'class'; if ($lastClass instanceof Interface_) { $classType = 'interface'; } elseif ($lastClass instanceof Trait_) { $classType = 'trait'; } $classTypePosition = strpos($fileString, sprintf('%s %s', $classType, $lastClass->name)); $nameResolveInfluencingPart = trim(substr($fileString, 0, $classTypePosition)); } else { $nameResolveInfluencingPart = $fileString; } if (substr($nameResolveInfluencingPart, -strlen('final')) === 'final') { $nameResolveInfluencingPart = trim(substr($nameResolveInfluencingPart, 0, -strlen('final'))); } if (substr($nameResolveInfluencingPart, -strlen('abstract')) === 'abstract') { $nameResolveInfluencingPart = trim(substr($nameResolveInfluencingPart, 0, -strlen('abstract'))); } $namespace = null; $uses = []; $this->processNodes($this->parser->parseString($nameResolveInfluencingPart), function (\PhpParser\Node $node) use($processTypeString, $patterns, &$namespace, &$uses) { if ($node instanceof \PhpParser\Node\Stmt\Namespace_) { $namespace = (string) $node->name; } elseif ($node instanceof \PhpParser\Node\Stmt\Use_ && $node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { foreach ($node->uses as $use) { $uses[$use->alias] = (string) $use->name; } } elseif ($node instanceof \PhpParser\Node\Stmt\GroupUse) { $prefix = (string) $node->prefix; foreach ($node->uses as $use) { if ($node->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL || $use->type === \PhpParser\Node\Stmt\Use_::TYPE_NORMAL) { $uses[$use->alias] = sprintf('%s\\%s', $prefix, $use->name); } } } }); foreach ($objectTypes as $key => $objectType) { $objectTypeType = $objectType['type']; $objectTypeTypeClass = $objectTypeType->getClass(); if (preg_match('#^[a-zA-Z_\\\\]#', $objectTypeTypeClass) === 0) { unset($objectTypes[$key]); continue; } if (strtolower($objectTypeTypeClass) === 'new') { unset($objectTypes[$key]); continue; } } foreach ($objectTypes as $objectType) { if (isset($objectType['arrayType'])) { $arrayType = $objectType['arrayType']; $typeMap[$objectType['typeString']] = ArrayType::createDeepArrayType(new NestedArrayItemType(new ObjectType($this->resolveStringName($namespace, $objectType['type']->getClass(), $uses), false), $arrayType['depth']), $arrayType['nullable']); } else { $objectTypeString = $objectType['typeString']; $objectTypeType = $objectType['type']; $typeMap[$objectTypeString] = new ObjectType($this->resolveStringName($namespace, $objectType['type']->getClass(), $uses), $objectTypeType->isNullable()); } } return $typeMap; }