z7zmey / php-parser

PHP parser written in Go

Home Page:https://php-parser.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NamespaceResolver should remove unresolved names

ruudk opened this issue · comments

I'm having issues with the namespace resolver. It contains unresolved names like void, true and null. Shouldn't these be removed when they are not resolved

test.php

<?php

declare(strict_types=1);

namespace App\Domain\Handler\Cart;

use SimpleBus\Message\Recorder\RecordsMessages;
use App\Domain\Command\ChangeCurrencyCommand;
use App\Domain\Repository\CartRepository;
use App\Domain\Event\CurrencyChangedEvent as CurrencyChangedEventWithAlias;

class ChangeCurrencyHandler
{
    /**
     * @var CartRepository
     */
    private $cartRepository;

    /**
     * @var RecordsMessages
     */
    private $eventRecorder;

    public function __construct(
        CartRepository $cartRepository,
        RecordsMessages $eventRecorder
    ) {
        $this->cartRepository   = $cartRepository;
        $this->eventRecorder    = $eventRecorder;
    }

    public function __invoke(ChangeCurrencyCommand $command) : void
    {
        if (true === $command->getBool()) {
            // Do something
        }
        
        if (null !== $command->getNull()) {
            // Do something
        }

        $this->eventRecorder->record(new CurrencyChangedEventWithAlias());
    }
}

main.go

package main

import (
	"fmt"
	"github.com/z7zmey/php-parser/php7"
	"github.com/z7zmey/php-parser/visitor"
	"os"
	"reflect"
)

func main() {
	for _, file := range os.Args[1:] {
		fmt.Printf("Checking %s\n", file)

		checkFile(file)
	}
}

func checkFile(file string) {
	src, err := os.Open(file)
	if err != nil {
		panic(err)
	}

	parser := php7.NewParser(src, file)
	parser.Parse()

	for _, e := range parser.GetErrors() {
		fmt.Println(e)
	}

	nsResolver := visitor.NewNamespaceResolver()
	parser.GetRootNode().Walk(nsResolver)

	for n, fqcn := range nsResolver.ResolvedNames {
		fmt.Printf("Found %s: %s\n", reflect.TypeOf(n), fqcn)
	}
}

output

Checking ./test.php
Found *name.Name: SimpleBus\Message\Recorder\RecordsMessages
Found *name.Name: App\Domain\Command\ChangeCurrencyCommand
Found *name.Name: void
Found *name.Name: true
Found *name.Name: null
Found *name.Name: App\Domain\Event\CurrencyChangedEvent
Found *stmt.Class: App\Domain\Handler\Cart\ChangeCurrencyHandler
Found *name.Name: App\Domain\Repository\CartRepository

I modified the namespace resolver with the following patch:

diff --git a/visitor/namespace_resolver.go b/visitor/namespace_resolver.go
index dc7f78f..31e8400 100644
--- a/visitor/namespace_resolver.go
+++ b/visitor/namespace_resolver.go
@@ -289,7 +289,7 @@ func (ns *Namespace) ResolveName(nameNode node.Node, aliasType string) (string,
                if aliasType == "const" && len(n.Parts) == 1 {
                        part := strings.ToLower(n.Parts[0].(*name.NamePart).Value)
                        if part == "true" || part == "false" || part == "null" {
-                               return part, nil
+                               return "", errors.New("ignore")
                        }
                }

@@ -316,7 +316,7 @@ func (ns *Namespace) ResolveName(nameNode node.Node, aliasType string) (string,
                        case "iterable":
                                fallthrough
                        case "object":
-                               return part, nil
+                               return "", errors.New("ignore")
                        }
                }

And now it outputs correctly

Checking ./test.php
Found *name.Name: App\Domain\Repository\CartRepository
Found *name.Name: SimpleBus\Message\Recorder\RecordsMessages
Found *name.Name: App\Domain\Command\ChangeCurrencyCommand
Found *name.Name: App\Domain\Event\CurrencyChangedEvent
Found *stmt.Class: App\Domain\Handler\Cart\ChangeCurrencyHandler

The main idea is that all *name.Name must have an appropriate resolved name. It allows working with them in a similar way. So when we traverse AST there is no need to check is it a function name or is it a name of a reserved constant.

<?
namespace foo;

function true() {
    return true;
}

But since they all are *name.Name there is no way to tell if it's a FQCN or something else?

There are few ways to resolve only fully qualified class names

First(simple) - write the same visitor that resolves only FQCN.
It must be implemented in the client code. In this way, you can apply your logic how to process nodes.

Second - add in the all nodes link to their parent node so you will able filter FQN by their parent nodes.
It most flexible approach but It would create an additional load at the parsing level.