Beispiel #1
0
 /**
  * @param string $sourceCode
  * @return \PhpParser\Node[]
  */
 public function parseString(string $sourceCode) : array
 {
     if (!isset($this->cachedNodesByString[$sourceCode])) {
         $this->cachedNodesByString[$sourceCode] = $this->originalParser->parseString($sourceCode);
     }
     return $this->cachedNodesByString[$sourceCode];
 }
Beispiel #2
0
 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;
 }
Beispiel #3
0
 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;
 }
Beispiel #5
0
 /**
  * @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;
 }
Beispiel #6
0
 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;
 }