<commit_before><?php

/*
 * This file is part of the Sami utility.
 *
 * (c) <NAME> <<EMAIL>>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sami\Parser;

use PhpParser\Node as AbstractNode;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Stmt as StmtNode;
use PhpParser\Node\Stmt\ClassConst as ClassConstNode;
use PhpParser\Node\Stmt\ClassMethod as ClassMethodNode;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassLike as ClassLikeNode;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PhpParser\Node\Stmt\Use_ as UseNode;
use Sami\Project;
use Sami\Reflection\ClassReflection;
use Sami\Reflection\ConstantReflection;
use Sami\Reflection\MethodReflection;
use Sami\Reflection\ParameterReflection;
use Sami\Reflection\PropertyReflection;

class NodeVisitor extends NodeVisitorAbstract
{
    protected $context;

    public function __construct(ParserContext $context)
    {
        $this->context = $context;
    }

    public function enterNode(AbstractNode $node)
    {
        if ($node instanceof NamespaceNode) {
            $this->context->enterNamespace((string) $node->name);
        } elseif ($node instanceof UseNode) {
            $this->addAliases($node);
        } elseif ($node instanceof InterfaceNode) {
            $this->addInterface($node);
        } elseif ($node instanceof ClassNode) {
            $this->addClass($node);
        } elseif ($node instanceof TraitNode) {
            $this->addTrait($node);
        } elseif ($this->context->getClass() && $node instanceof TraitUseNode) {
            $this->addTraitUse($node);
        } elseif ($this->context->getClass() && $node instanceof PropertyNode) {
            $this->addProperty($node);
        } elseif ($this->context->getClass() && $node instanceof ClassMethodNode) {
            $this->addMethod($node);
        } elseif ($this->context->getClass() && $node instanceof ClassConstNode) {
            $this->addConstant($node);
        }
    }

    public function leaveNode(AbstractNode $node)
    {
        if ($node instanceof NamespaceNode) {
            $this->context->leaveNamespace();
        } elseif ($node instanceof ClassNode || $node instanceof InterfaceNode || $node instanceof TraitNode) {
            $this->context->leaveClass();
        }
    }

    protected function addAliases(UseNode $node)
    {
        foreach ($node->uses as $use) {
            $this->context->addAlias($use->alias, (string) $use->name);
        }
    }

    protected function addInterface(InterfaceNode $node)
    {
        $class = $this->addClassOrInterface($node);

        $class->setInterface(true);
        foreach ($node->extends as $interface) {
            $class->addInterface((string) $interface);
        }
    }

    protected function addClass(ClassNode $node)
    {
        $class = $this->addClassOrInterface($node);

        foreach ($node->implements as $interface) {
            $class->addInterface((string) $interface);
        }

        if ($node->extends) {
            $class->setParent((string) $node->extends);
        }
    }

    protected function addTrait(TraitNode $node)
    {
        $class = $this->addClassOrInterface($node);

        $class->setTrait(true);
    }

    protected function addClassOrInterface(ClassLikeNode $node)
    {
        $class = new ClassReflection((string) $node->namespacedName, $node->getLine());
        if ($class instanceof ClassNode) {
            $class->setModifiers($node->flags);
        }
        $class->setNamespace($this->context->getNamespace());
        $class->setAliases($this->context->getAliases());
        $class->setHash($this->context->getHash());
        $class->setFile($this->context->getFile());

        $comment = $this->context->getDocBlockParser()->parse($node->getDocComment(), $this->context, $class);
        $class->setDocComment($node->getDocComment());
        $class->setShortDesc($comment->getShortDesc());
        $class->setLongDesc($comment->getLongDesc());
        if ($errors = $comment->getErrors()) {
            $class->setErrors($errors);
        } else {
            $class->setTags($comment->getOtherTags());
        }

        if ($this->context->getFilter()->acceptClass($class)) {
            if ($errors) {
                $this->context->addErrors((string) $class, $node->getLine(), $errors);
            }
            $this->context->enterClass($class);
        }

        return $class;
    }

    protected function addMethod(ClassMethodNode $node)
    {
        $method = new MethodReflection($node->name, $node->getLine());
        $method->setModifiers($node->flags);
        $method->setByRef((string) $node->byRef);

        foreach ($node->params as $param) {
            $parameter = new ParameterReflection($param->name, $param->getLine());
            $parameter->setModifiers($param->type);
            $parameter->setByRef($param->byRef);
            if ($param->default) {
                $parameter->setDefault($this->context->getPrettyPrinter()->prettyPrintExpr($param->default));
            }

            if ($type = (string) $param->type) {
                if ($param->type instanceof FullyQualified && strpos($type, '\\') !== 0) {
                    $type = '\\'.$type;
                }

                $parameter->setHint($this->resolveHint(array(array($type, false))));
            }
            $method->addParameter($parameter);
        }

        $comment = $this->context->getDocBlockParser()->parse($node->getDocComment(), $this->context, $method);
        $method->setDocComment($node->getDocComment());
        $method->setShortDesc($comment->getShortDesc());
        $method->setLongDesc($comment->getLongDesc());
        if (!$errors = $comment->getErrors()) {
            $errors = $this->updateMethodParametersFromTags($method, $comment->getTag('param'));

            if ($tag = $comment->getTag('return')) {
                $method->setHint($this->resolveHint($tag[0][0]));
                $method->setHintDesc($tag[0][1]);
            }

            $method->setExceptions($comment->getTag('throws'));
            $method->setTags($comment->getOtherTags());
        }
<commit_msg>Fixing https://github.com/FriendsOfPHP/Sami/issues/264 : sami fails to parse nullable types with php 7.1<commit_after><?php

/*
 * This file is part of the Sami utility.
 *
 * (c) <NAME> <<EMAIL>>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sami\Parser;

use PhpParser\Node as AbstractNode;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Stmt\ClassConst as ClassConstNode;
use PhpParser\Node\Stmt\ClassMethod as ClassMethodNode;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassLike as ClassLikeNode;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PhpParser\Node\Stmt\Use_ as UseNode;
use PhpParser\Node\NullableType;
use Sami\Project;
use Sami\Reflection\ClassReflection;
use Sami\Reflection\ConstantReflection;
use Sami\Reflection\MethodReflection;
use Sami\Reflection\ParameterReflection;
use Sami\Reflection\PropertyReflection;

class NodeVisitor extends NodeVisitorAbstract
{
    protected $context;

    public function __construct(ParserContext $context)
    {
        $this->context = $context;
    }

    public function enterNode(AbstractNode $node)
    {
        if ($node instanceof NamespaceNode) {
            $this->context->enterNamespace((string) $node->name);
        } elseif ($node instanceof UseNode) {
            $this->addAliases($node);
        } elseif ($node instanceof InterfaceNode) {
            $this->addInterface($node);
        } elseif ($node instanceof ClassNode) {
            $this->addClass($node);
        } elseif ($node instanceof TraitNode) {
            $this->addTrait($node);
        } elseif ($this->context->getClass() && $node instanceof TraitUseNode) {
            $this->addTraitUse($node);
        } elseif ($this->context->getClass() && $node instanceof PropertyNode) {
            $this->addProperty($node);
        } elseif ($this->context->getClass() && $node instanceof ClassMethodNode) {
            $this->addMethod($node);
        } elseif ($this->context->getClass() && $node instanceof ClassConstNode) {
            $this->addConstant($node);
        }
    }

    public function leaveNode(AbstractNode $node)
    {
        if ($node instanceof NamespaceNode) {
            $this->context->leaveNamespace();
        } elseif ($node instanceof ClassNode || $node instanceof InterfaceNode || $node instanceof TraitNode) {
            $this->context->leaveClass();
        }
    }

    protected function addAliases(UseNode $node)
    {
        foreach ($node->uses as $use) {
            $this->context->addAlias($use->alias, (string) $use->name);
        }
    }

    protected function addInterface(InterfaceNode $node)
    {
        $class = $this->addClassOrInterface($node);

        $class->setInterface(true);
        foreach ($node->extends as $interface) {
            $class->addInterface((string) $interface);
        }
    }

    protected function addClass(ClassNode $node)
    {
        $class = $this->addClassOrInterface($node);

        foreach ($node->implements as $interface) {
            $class->addInterface((string) $interface);
        }

        if ($node->extends) {
            $class->setParent((string) $node->extends);
        }
    }

    protected function addTrait(TraitNode $node)
    {
        $class = $this->addClassOrInterface($node);

        $class->setTrait(true);
    }

    protected function addClassOrInterface(ClassLikeNode $node)
    {
        $class = new ClassReflection((string) $node->namespacedName, $node->getLine());
        if ($class instanceof ClassNode) {
            $class->setModifiers($node->flags);
        }
        $class->setNamespace($this->context->getNamespace());
        $class->setAliases($this->context->getAliases());
        $class->setHash($this->context->getHash());
        $class->setFile($this->context->getFile());

        $comment = $this->context->getDocBlockParser()->parse($node->getDocComment(), $this->context, $class);
        $class->setDocComment($node->getDocComment());
        $class->setShortDesc($comment->getShortDesc());
        $class->setLongDesc($comment->getLongDesc());
        if ($errors = $comment->getErrors()) {
            $class->setErrors($errors);
        } else {
            $class->setTags($comment->getOtherTags());
        }

        if ($this->context->getFilter()->acceptClass($class)) {
            if ($errors) {
                $this->context->addErrors((string) $class, $node->getLine(), $errors);
            }
            $this->context->enterClass($class);
        }

        return $class;
    }

    protected function addMethod(ClassMethodNode $node)
    {
        $method = new MethodReflection($node->name, $node->getLine());
        $method->setModifiers($node->flags);
        $method->setByRef((string) $node->byRef);

        foreach ($node->params as $param) {
            $parameter = new ParameterReflection($param->name, $param->getLine());
            $parameter->setModifiers($param->type);
            $parameter->setByRef($param->byRef);
            if ($param->default) {
                $parameter->setDefault($this->context->getPrettyPrinter()->prettyPrintExpr($param->default));
            }

            $type = $param->type;
            $typeStr = NULL;

            if (is_string($param->type)) {
                $type = $param->type;
                $typeStr = (string) $param->type;

            }
            else if ($param->type instanceof NullableType){
                $type = $param->type->type;
                $typeStr = (string) $param->type->type;
            }

            if ($type instanceof FullyQualified && strpos($typeStr, '\\') !== 0) {
                $typeStr = '\\'.$typeStr;
            }
                
            if ($typeStr != NULL) {
                $parameter->setHint($this->resolveHint(array(array($typeStr, false))));
            }

            $method->addParameter($parameter);
        }

        $comment = $this->context->getDocBlockParser()->parse($node->getDocComment(), $this->context, $method);
        $method->setDocComment($node->getDocComment());
        $method->setShortDesc($comment->getShortDesc());
        $method->setLongDesc($comment->getLongDesc());
        if (!$errors = $comment->getErrors()) {
            $errors = $this->updateMethodParametersFromTags($method, $comment->getTag('param'));

            if ($tag = $comment->getTag('return')) {
                $method->setHint($this->resolveHint($tag[0][0]));
                $method->setHintDesc($tag[0][1]);
            }

            $method->setExceptions($comment->getTag('throws'));
            $method->setTags($comment->getOtherTags());
        }

{'fraction_non_alphanumeric': 0.15115845936079864, 'fraction_numerical': 0.0011174849139536616, 'mean_word_length': 2.6517954298150164, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 268, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4197789', 'n_tokens_mistral': 3750, 'n_tokens_neox': 3717, 'n_words': 692}