Completed
Push — master ( db8578...85f9c3 )
by Kirill
10:30
created

PP2::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
ccs 0
cts 7
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\Compiler\Grammar;
11
12
use Railt\Compiler\Grammar\PP2\Delegate\IncludeDelegate;
13
use Railt\Compiler\Grammar\PP2\Delegate\PragmaDefinitionDelegate;
14
use Railt\Compiler\Grammar\PP2\Delegate\RuleDelegate;
15
use Railt\Compiler\Grammar\PP2\Delegate\TokenDefinitionDelegate;
16
use Railt\Compiler\Grammar\PP2\Mapping;
17
use Railt\Compiler\Grammar\PP2\Parser;
18
use Railt\Compiler\Grammar\PP2\Resolvers\PragmasResolver;
19
use Railt\Compiler\Grammar\PP2\Resolvers\RulesResolver;
20
use Railt\Compiler\Grammar\PP2\Resolvers\TokensResolver;
21
use Railt\Compiler\Reader\GrammarInterface;
22
use Railt\Compiler\Reader\ProvidePragmas;
23
use Railt\Compiler\Reader\ProvideRules;
24
use Railt\Compiler\Reader\ProvideTokens;
25
use Railt\Compiler\Reader\Result;
26
use Railt\Io\Readable;
27
use Railt\Parser\Ast\LeafInterface;
28
use Railt\Parser\Ast\RuleInterface;
29
use Railt\Parser\Environment;
30
use Railt\Parser\Exception\UnrecognizedRuleException;
31
32
/**
33
 * Class Grammar
34
 */
35
class PP2 implements GrammarInterface
36
{
37
    public const ENV_MAP = 'map';
38
    public const ENV_FILE = 'file';
39
    public const ENV_RULES = 'rules';
40
    public const ENV_TOKENS = 'tokens';
41
    public const ENV_PRAGMAS = 'pragmas';
42
43
    /**
44
     * @var array|string[]
45
     */
46
    private $loaded = [];
47
48
    /**
49
     * @var PP2
50
     */
51
    private $parser;
52
53
    /**
54
     * @var Environment
55
     */
56
    private $env;
57
58
    /**
59
     * @var ProvidePragmas|PragmasResolver
60
     */
61
    private $pragmas;
62
63
    /**
64
     * @var ProvideRules|RulesResolver
65
     */
66
    private $rules;
67
68
    /**
69
     * @var ProvideTokens|TokensResolver
70
     */
71
    private $tokens;
72
73
    /**
74
     * @var Mapping
75
     */
76
    private $map;
77
78
    /**
79
     * PP2 constructor.
80
     * @throws \InvalidArgumentException
81
     */
82
    public function __construct()
83
    {
84
        $this->parser = new Parser();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Railt\Compiler\Grammar\PP2\Parser() of type object<Railt\Compiler\Grammar\PP2\Parser> is incompatible with the declared type object<Railt\Compiler\Grammar\PP2> of property $parser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
85
        $this->map = new Mapping();
86
87
        $this->bootResolvers();
88
        $this->bootEnvironment();
89
    }
90
91
    /**
92
     * @return void
93
     */
94
    private function bootResolvers(): void
95
    {
96
        $this->pragmas = new PragmasResolver();
97
        $this->rules   = new RulesResolver($this->map);
98
        $this->tokens  = new TokensResolver();
99
    }
100
101
    /**
102
     * @return void
103
     */
104
    private function bootEnvironment(): void
105
    {
106
        $this->env = $this->createEnvironment();
107
    }
108
109
    /**
110
     * @return Environment
111
     */
112
    private function createEnvironment(): Environment
113
    {
114
        $env = new Environment();
115
        $env->share(static::ENV_MAP, $this->map);
116
        $env->share(static::ENV_RULES, $this->rules);
117
        $env->share(static::ENV_TOKENS, $this->tokens);
118
        $env->share(static::ENV_PRAGMAS, $this->pragmas);
119
120
        return $env;
121
    }
122
123
    /**
124
     * @return Result
125
     */
126
    public function make(): Result
127
    {
128
        return new Result($this->pragmas, $this->tokens, $this->rules);
129
    }
130
131
    /**
132
     * @param Readable $grammar
133
     * @throws UnrecognizedRuleException
134
     * @throws \LogicException
135
     * @throws \Railt\Io\Exception\ExternalFileException
136
     */
137
    private function load(Readable $grammar): void
138
    {
139
        /** @var RuleInterface|LeafInterface $rule */
140
        foreach ($this->parse($grammar) as $rule) {
141
            switch ($rule->getName()) {
142
                case 'Pragma':
143
                    /** @var PragmaDefinitionDelegate $rule */
144
                    $this->pragmas->resolve($grammar, $rule);
0 ignored issues
show
Bug introduced by
The method resolve does only exist in Railt\Compiler\Grammar\P...solvers\PragmasResolver, but not in Railt\Compiler\Reader\ProvidePragmas.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
145
                    break;
146
147
                case 'Include':
148
                    /** @var IncludeDelegate $rule */
149
                    $this->add($rule->getFile());
150
                    break;
151
152
                case 'Rule':
153
                    /** @var RuleDelegate $rule */
154
                    $this->rules->resolve($grammar, $rule);
0 ignored issues
show
Bug introduced by
The method resolve does only exist in Railt\Compiler\Grammar\PP2\Resolvers\RulesResolver, but not in Railt\Compiler\Reader\ProvideRules.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
155
                    break;
156
157
                case 'Token':
158
                    /** @var TokenDefinitionDelegate $rule */
159
                    $this->tokens->resolve($grammar, $rule);
0 ignored issues
show
Bug introduced by
The method resolve does only exist in Railt\Compiler\Grammar\P...esolvers\TokensResolver, but not in Railt\Compiler\Reader\ProvideTokens.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
160
                    break;
161
162
                default:
163
                    $error = \sprintf('Unprocessable rule %s', $rule->getName());
164
                    throw (new UnrecognizedRuleException($error))->throwsIn($grammar, $rule->getOffset());
165
            }
166
        }
167
    }
168
169
    /**
170
     * @param Readable $grammar
171
     * @return RuleInterface
172
     * @throws \LogicException
173
     * @throws \Railt\Io\Exception\ExternalFileException
174
     * @throws \Railt\Parser\Exception\UnrecognizedRuleException
175
     */
176
    private function parse(Readable $grammar): RuleInterface
177
    {
178
        return $this->parser->parse($grammar, $this->env);
0 ignored issues
show
Unused Code introduced by
The call to PP2::parse() has too many arguments starting with $this->env.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
179
    }
180
181
    /**
182
     * @param Readable $grammar
183
     * @return GrammarInterface
184
     * @throws \LogicException
185
     * @throws \Railt\Io\Exception\ExternalFileException
186
     * @throws \Railt\Parser\Exception\UnrecognizedRuleException
187
     */
188
    public function add(Readable $grammar): GrammarInterface
189
    {
190
        if (! $this->isLoaded($grammar)) {
191
            $this->env->share(static::ENV_FILE, $grammar);
192
            $this->load($grammar);
193
        }
194
195
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Railt\Compiler\Grammar\PP2) is incompatible with the return type declared by the interface Railt\Compiler\Reader\GrammarInterface::add of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
196
    }
197
198
    /**
199
     * @param Readable $grammar
200
     * @return bool
201
     */
202
    private function isLoaded(Readable $grammar): bool
203
    {
204
        $loaded = \in_array($grammar->getHash(), $this->loaded, true);
205
206
        if (! $loaded) {
207
            $this->loaded[] = $grammar->getHash();
208
        }
209
210
        return $loaded;
211
    }
212
}
213