Completed
Push — master ( 14784f...87aa66 )
by Richard
05:56
created

Indexer::afterTraverse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the licence along with the code.
9
 */
10
11
namespace Lechimp\Dicto\Indexer\PhpParser;
12
13
use Lechimp\Dicto\Indexer as I;
14
use Lechimp\Dicto\Analysis\Consts;
15
use PhpParser\Node as N;
16
17
/**
18
 * Implementation of Indexer with PhpParser.
19
 */
20
class Indexer implements I\Indexer,  \PhpParser\NodeVisitor {
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "PhpParser"; 2 found
Loading history...
21
    /**
22
     * @var string
23
     */
24
    protected $project_root_path;
25
26
    /**
27
     * @var I\Insert|null
28
     */
29
    protected $insert;
30
31
    /**
32
     * @var \PhpParser\Parser
33
     */
34
    protected $parser;
35
36
    /**
37
     * @var Listener[];
38
     */
39
    protected $listeners;
40
41
    // state for parsing a file
42
43
    /**
44
     * @var string|null
45
     */
46
    protected $file_path = null;
47
48
    /**
49
     * @var string[]|null
50
     */
51
    protected $file_content = null;
52
53
    /**
54
     * This contains the stack of ids were currently in, i.e. the nesting of
55
     * known code blocks we are in.
56
     *
57
     * @var int[]|null
58
     */
59
    protected $entity_id_stack = null;
60
61
    /**
62
     * This contains cached reference ids.
63
     *
64
     * @var array|null   string => int 
65
     */
66
    protected $reference_cache = null; 
67
    
68
69 17
    public function __construct(\PhpParser\Parser $parser) {
70 17
        $this->project_root_path = "";
71 17
        $this->parser = $parser;
72 17
        $this->listeners = array();
73 17
    }
74
75 17
    protected function build_listeners() {
76
        return array
77 17
            ( new DependenciesListener($this->insert, $this)
0 ignored issues
show
Bug introduced by
It seems like $this->insert can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
78 17
            , new InvocationsListener($this->insert, $this)
0 ignored issues
show
Bug introduced by
It seems like $this->insert can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
79 17
            );
80
    }
81
82
    /**
83
     * @inheritdoc
84
     */
85 16
    public function index_file($path) {
86 16
        if ($this->insert === null) {
87
            throw new \RuntimeException(
88
                "Set an inserter to be used before starting to index files.");
89
        }
90
91 16
        $content = file_get_contents($this->project_root_path."/$path");
92 16
        if ($content === false) {
93
            throw \InvalidArgumentException("Can't read file $path.");
94
        }
95
96 16
        $stmts = $this->parser->parse($content);
97
98 16
        if ($stmts === null) {
99
            throw new \RuntimeException("Could not parse file '$path'.");
100
        }
101
102 16
        $traverser = new \PhpParser\NodeTraverser;
103 16
        $traverser->addVisitor($this);
104
105 16
        $this->entity_id_stack = array();
106 16
        $this->file_path = $path;
107 16
        $this->file_content = explode("\n", $content);
108 16
        $this->reference_cache = array();
109 16
        $traverser->traverse($stmts);
110 16
        $this->entity_id_stack = null; 
111 16
        $this->file_path = null;
112 16
        $this->file_content = null;
113 16
        $this->reference_cache = null;
114 16
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119 17
    public function use_insert(I\Insert $insert) {
120 17
        $this->insert = $insert;
121 17
        $this->listeners = $this->build_listeners();
122 17
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127 17
    public function set_project_root_to($path) {
128 17
        assert('is_string($path)');
129 17
        $this->project_root_path = $path;
130 17
    }
131
132
    // helper
133
134 16
    private function lines_from_to($start, $end) {
135 16
        assert('is_int($start)');
136 16
        assert('is_int($end)');
137 16
        return implode("\n", array_slice($this->file_content, $start-1, $end-$start+1));
138
    }
139
140 14
    public function get_reference($entity_type, $name, $start_line) {
141 14
        assert('in_array($entity_type, \\Lechimp\\Dicto\\Analysis\\Consts::$ENTITY_TYPES)');
142 14
        assert('is_string($name)');
143 14
        assert('is_int($start_line)');
144
145
        // caching
146 14
        $key = $entity_type.":".$name.":".$this->file_path.":".$start_line;
147 14
        if (array_key_exists($key, $this->reference_cache)) {
148 12
            return $this->reference_cache[$key];
149
        }
150
151 14
        $ref_id = $this->insert->reference
152 14
            ( $entity_type 
153 14
            , $name 
154 14
            , $this->file_path
155 14
            , $start_line
156 14
            );
157
158 14
        $this->reference_cache[$key] = $ref_id;
159 14
        return $ref_id;
160
    }
161
162
    // from \PhpParser\NodeVisitor
163
164
    /**
165
     * @inheritdoc
166
     */
167 16
    public function beforeTraverse(array $nodes) {
168
        // for sure found a file
169 16
        $id = $this->insert->entity
170 16
            ( Consts::FILE_ENTITY
171 16
            , $this->file_path
172 16
            , $this->file_path
173 16
            , 1
174 16
            , count($this->file_content)
175 16
            , implode("\n", $this->file_content)
176 16
            );
177
178 16
        $this->entity_id_stack[] = $id;
179
180 16
        foreach ($this->listeners as $listener) {
181 16
            $listener->on_enter_file($id, $this->file_path, implode("\n", $this->file_content));
182 16
        }
183
184 16
        return null;
185
    }
186
187
    /**
188
     * @inheritdoc
189
     */
190 16
    public function afterTraverse(array $nodes) {
191 16
        $id = array_pop($this->entity_id_stack);
192
193 16
        foreach ($this->listeners as $listener) {
194 16
            $listener->on_leave_file($id);
195 16
        }
196
197 16
        return null;
198
    }
199
200
    /**
201
     * @inheritdoc
202
     */
203 16
    public function enterNode(\PhpParser\Node $node) {
204 16
        $start_line = $node->getAttribute("startLine");
205 16
        $end_line = $node->getAttribute("endLine");
206 16
        $source = $this->lines_from_to($start_line, $end_line);
207
208 16
        $id = null;
209
210
        // Class
211 16
        if ($node instanceof N\Stmt\Class_) {
212 15
            $id = $this->insert->entity
213 15
                ( Consts::CLASS_ENTITY
214 15
                , $node->name
215 15
                , $this->file_path
216 15
                , $start_line
217 15
                , $end_line
218 15
                , $source
219 15
                );
220
221 15
            foreach ($this->listeners as $listener) {
222 15
                $listener->on_enter_class($id, $node);
223 15
            }
224 15
        }
225
        // Method or Function
226 16 View Code Duplication
        elseif ($node instanceof N\Stmt\ClassMethod) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227 15
            $id = $this->insert->entity
228 15
                ( Consts::METHOD_ENTITY
229 15
                , $node->name
230 15
                , $this->file_path
231 15
                , $start_line
232 15
                , $end_line
233 15
                , $source
234 15
                );
235
236 15
            foreach ($this->listeners as $listener) {
237 15
                $listener->on_enter_method($id, $node);
238 15
            }
239 15
        }
240 16 View Code Duplication
        elseif ($node instanceof N\Stmt\Function_) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
            $id = $this->insert->entity
242
                ( Consts::FUNCTION_ENTITY
243
                , $node->name
244
                , $this->file_path
245
                , $start_line
246
                , $end_line
247
                , $source
248
                );
249
250
            foreach ($this->listeners as $listener) {
251
                $listener->on_enter_function($id, $node);
252
            }
253
        }
254
        else {
255 16
            foreach ($this->listeners as $listener) {
256 16
                $listener->on_enter_misc($node);
257 16
            }
258
        }
259
260
        // Call to method or function
261 16
        if ($id !== null) {
262 15
            $this->entity_id_stack[] = $id;
263 15
        }
264 16
    }
265
266
    /**
267
     * @inheritdoc
268
     */
269 16
    public function leaveNode(\PhpParser\Node $node) {
270
        // Class
271 16
        if ($node instanceof N\Stmt\Class_) {
272
            // We pushed it, we need to pop it now, as we are leaving the class.
273 15
            $id = array_pop($this->entity_id_stack);
274
275 15
            foreach ($this->listeners as $listener) {
276 15
                $listener->on_leave_class($id);
277 15
            }
278 15
        }
279
        // Method or Function
280 16
        elseif ($node instanceof N\Stmt\ClassMethod) {
281
            // We pushed it, we need to pop it now, as we are leaving the method
282
            // or function.
283 15
            $id = array_pop($this->entity_id_stack);
284
285 15
            foreach ($this->listeners as $listener) {
286 15
                $listener->on_leave_method($id);
287 15
            }
288 15
        }
289 16
        elseif ($node instanceof N\Stmt\Function_) {
290
            // We pushed it, we need to pop it now, as we are leaving the method
291
            // or function.
292
            $id = array_pop($this->entity_id_stack);
293
294
            foreach ($this->listeners as $listener) {
295
                $listener->on_leave_function($id);
296
            }
297
        }
298 16
    }
299
}
300