Completed
Push — master ( c72839...dd8e38 )
by Thomas
02:47
created

Context::getBlockContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
namespace gossi\formatter\parser;
3
4
use gossi\formatter\entities\Block;
5
use gossi\formatter\entities\Group;
6
use gossi\formatter\events\BlockEvent;
7
use gossi\formatter\events\GroupEvent;
8
use phootwork\collection\Stack;
9
use phootwork\tokenizer\Token;
10
use phootwork\tokenizer\TokenVisitorInterface;
11
use Symfony\Component\EventDispatcher\EventDispatcher;
12
13
class Context implements TokenVisitorInterface {
14
15
	// context constants
16
	const CONTEXT_FILE = 'file';
17
	const CONTEXT_STRUCT = 'struct';
18
	const CONTEXT_ROUTINE = 'routine';
19
	const CONTEXT_BLOCK = 'block';
20
	const CONTEXT_GROUP = 'group';
21
22
	// event constants
23
	const EVENT_BLOCK_ENTER = 'context.block_enter';
24
	const EVENT_BLOCK_LEAVE = 'context.block_leave';
25
	const EVENT_STRUCT_ENTER = 'context.struct_enter';
26
	const EVENT_STRUCT_LEAVE = 'context.struct_leave';
27
	const EVENT_ROUTINE_ENTER = 'context.routine_enter';
28
	const EVENT_ROUTINE_LEAVE = 'context.routine_leave';
29
	const EVENT_GROUP_ENTER = 'context.group_enter';
30
	const EVENT_GROUP_LEAVE = 'context.group_leave';
31
32
	// current contexts
33
	private $block;
34
	private $group;
35
	private $inStructBody = false;
36
	private $inRoutineBody = false;
37
38
	// stacks
39
	private $blockStack;
40
	private $groupStack;
41
	private $contextStack;
42
	private $line;
43
44
	// helpers
45
	private $blockDetected;
46
	private $tracker;
47
	private $dispatcher;
48
	private $parser;
49
	private $matcher;
50
	private $events = [
51
		self::EVENT_BLOCK_ENTER, self::EVENT_BLOCK_LEAVE,
52
		self::EVENT_GROUP_ENTER, self::EVENT_GROUP_LEAVE,
53
		self::EVENT_ROUTINE_ENTER, self::EVENT_ROUTINE_LEAVE,
54
		self::EVENT_STRUCT_ENTER, self::EVENT_STRUCT_LEAVE
55
	];
56
57
	private static $PROPERTIES = [T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC, T_VAR];
0 ignored issues
show
Unused Code introduced by
The property $PROPERTIES is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
58
59 10
	public function __construct(Parser $parser) {
60 10
		$this->parser = $parser;
61 10
		$this->matcher = $parser->getMatcher();
62 10
		$this->blockStack = new Stack();
63 10
		$this->groupStack = new Stack();
64 10
		$this->contextStack = new Stack();
65 10
		$this->dispatcher = new EventDispatcher();
66 10
	}
67
68 10
	public function reset() {
69
		// remove listeners
70 10
		foreach ($this->events as $event) {
71 10
			$listeners = $this->dispatcher->getListeners($event);
72 10
			foreach ($listeners as $listener) {
73 10
				$this->dispatcher->removeListener($event, $listener);
74 10
			}
75 10
		}
76
77
		// reset data objects
78 10
		$this->blockStack->clear();
79 10
		$this->groupStack->clear();
80 10
		$this->contextStack->clear();
81 10
	}
82
83 10
	public function setTracker(TokenTracker $tracker) {
84 10
		$this->tracker = $tracker;
85 10
	}
86
87 10
	public function addListener($name, $listener) {
88 10
		$this->dispatcher->addListener($name, $listener);
89 10
	}
90
91
	public function removeListener($name, $listener) {
92
		$this->dispatcher->removeListener($name, $listener);
93
	}
94
95 10
	public function visitToken(Token $token) {
96
		// load current contexts
97 10
		$this->block = $this->peekBlock();
98 10
		$this->group = $this->peekGroup();
99
100
		// detect new contexts
101 10
		$this->detectBlockContext($token);
102 10
		$this->detectGroupContext($token);
103 10
		$this->detectLineContext($token);
104 10
	}
105
106 10
	private function detectBlockContext(Token $token) {
107 10
		if ($this->matcher->isBlock($token)) {
108 10
			$this->blockDetected = $token;
109 10
		}
110
111 10
		$this->enterBlockContext($token);
112 10
		$this->leaveBlockContext($token);
113
114
		// neglect block detection
115 10
		if ($this->blockDetected !== null && $token->contents == ';'
116 10
				&& ($this->group !== null ? !($this->group->type == Group::BLOCK
117 10
					|| $this->group->type == Group::GROUP) : true)) {
118 10
			$this->blockDetected = null;
119 10
		}
120 10
	}
121
122 10
	private function enterBlockContext(Token $token) {
123 10
		if ($token->contents == '{' && $this->blockDetected !== null) {
124 10
			$type = Block::getType($this->blockDetected);
125 10
			if ($type == Block::TYPE_FUNCTION && $this->getCurrentContext() == self::CONTEXT_STRUCT) {
126 5
				$type = Block::TYPE_METHOD;
127 5
			}
128 10
			$block = new Block($type);
129 10
			$block->start = $this->findBlockStart($this->blockDetected);
130 10
			$block->open = $token;
131 10
			$this->blockStack->push($block);
132 10
			$this->block = $block;
133 10
			$this->blockDetected = null;
134
135 10
			$event = new BlockEvent($token, $this->block);
136 10
			$this->dispatcher->dispatch(self::EVENT_BLOCK_ENTER, $event);
137
138
			// enter struct context
139 10
			if ($block->isStruct()) {
140 5
				$this->inStructBody = true;
141 5
				$this->dispatcher->dispatch(self::EVENT_STRUCT_ENTER, $event);
142 5
				$this->contextStack->push(self::CONTEXT_STRUCT);
143 5
			}
144
145
			// enter routine context
146 10
			else if ($block->isRoutine()) {
147 5
				$this->inRoutineBody = true;
148 5
				$this->dispatcher->dispatch(self::EVENT_ROUTINE_ENTER, $event);
149 5
				$this->contextStack->push(self::CONTEXT_ROUTINE);
150 5
			}
151
152
			// enter block context
153
			else {
154 8
				$this->contextStack->push(self::CONTEXT_BLOCK);
155
			}
156 10
		}
157 10
	}
158
159 10
	private function findBlockStart(Token $token) {
160 10
		$startToken = $token;
161 10
		$prevToken = $this->tracker->prevToken($token);
162
163 10
		while ($this->matcher->isModifier($prevToken)) {
164 5
			$startToken = $prevToken;
165 5
			$prevToken = $this->tracker->prevToken($prevToken);
166 5
		}
167
168 10
		return $startToken;
169
	}
170
171 10
	private function leaveBlockContext(Token $token) {
172 10
		if ($token->contents == '}') {
173 10
			$this->block = $this->blockStack->pop();
174
175
			// find block end
176 10
			if ($this->block->type == Block::TYPE_DO) {
177 5
				$nextToken = $token;
178
				do {
179 5
					$nextToken = $this->tracker->nextToken($nextToken);
180 5
				} while ($nextToken->contents != ';');
181 5
				$this->block->end = $nextToken;
182 5
			} else {
183 10
				$this->block->end = $token;
184
			}
185
186 10
			$event = new BlockEvent($token, $this->block);
187 10
			$this->dispatcher->dispatch(self::EVENT_BLOCK_LEAVE, $event);
188 10
			$this->contextStack->pop();
189
190
			// leave struct context
191 10
			if ($this->inStructBody && $this->block->isStruct()) {
192 5
				$this->inStructBody = false;
193 5
				$this->dispatcher->dispatch(self::EVENT_STRUCT_LEAVE, $event);
194 5
			}
195
196
			// leave routine context
197 10
			else if ($this->inRoutineBody && $this->block->isRoutine()) {
198 5
				$this->inRoutineBody = false;
199 5
				$this->dispatcher->dispatch(self::EVENT_ROUTINE_LEAVE, $event);
200 5
			}
201 10
		}
202 10
	}
203
204
	public function isBlockContextDetected() {
205
		return $this->blockDetected !== null;
206
	}
207
208
	/**
209
	 * Tells you, whenever being in a struct, this is also true when inside 
210
	 * a method or inside a function, which is inside a method
211
	 * 
212
	 * @return bool
213
	 */
214
	public function inStructBody() {
215
		return $this->inStructBody;
216
	}
217
218
	/**
219
	 * Tells you, whenever being in a function or method
220
	 * 
221
	 * @return bool
222
	 */
223
	public function inRoutineBody() {
224
		return $this->inRoutineBody;
225
	}
226
227 10
	private function detectGroupContext(Token $token) {
228 10
		$prevToken = $this->tracker->getPrevToken();
229 10
		if ($token->contents == '(') {
230 10
			$group = new Group();
231 10
			$group->start = $token;
0 ignored issues
show
Documentation Bug introduced by
It seems like $token of type object<phootwork\tokenizer\Token> is incompatible with the declared type object<gossi\formatter\entities\Token> of property $start.

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...
232
// 			if (in_array($prevToken->type, Tokenizer::$BLOCKS)
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
233
// 					|| in_array($prevToken->type, Tokenizer::$OPERATORS)) {
234 10
			if (($this->matcher->isBlock($prevToken) || $this->matcher->isOperator($prevToken))
235 10
					&& !$this->group->isBlock()) {
236 8
				$group->type = Group::BLOCK;
237 8
				$group->token = $prevToken;
0 ignored issues
show
Documentation Bug introduced by
It seems like $prevToken of type object<phootwork\tokenizer\Token> is incompatible with the declared type object<gossi\formatter\entities\Token> of property $token.

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...
238 10
			} else if ($this->isFunctionInvocation($token)) {
239 10
				$group->type = Group::CALL;
240 10
				$group->token = $prevToken;
241 10
			} else {
242 5
				$group->type = Group::GROUP;
243
			}
244
245 10
			$this->groupStack->push($group);
246 10
			$this->group = $group;
247
248 10
			$event = new GroupEvent($token, $group);
249 10
			$this->dispatcher->dispatch(self::EVENT_GROUP_ENTER, $event);
250 10
		} else if ($token->contents == ')') {
251 10
			$this->group = $this->groupStack->pop();
252 10
			$this->group->end = $token;
253
254 10
			$event = new GroupEvent($token, $this->group);
255 10
			$this->dispatcher->dispatch(self::EVENT_GROUP_LEAVE, $event);
256 10
		}
257 10
	}
258
259 10
	private function isFunctionInvocation($token) {
260 10
		$prevToken = $this->tracker->getPrevToken();
261 10
		return $token->contents == '(' && $prevToken->type == T_STRING;
262
	}
263
264 10
	private function detectLineContext(Token $token) {
265 10
		if ($this->matcher->isLineContext($token)) {
266 2
			$this->line = $token->contents;
267 2
		}
268 10
	}
269
270 2
	public function resetLineContext() {
271 2
		$this->line = null;
272 2
	}
273
274
	/**
275
	 * Returns the line context or null if not present
276
	 * 
277
	 * @return string|null
278
	 */
279 2
	public function getLineContext() {
280 2
		return $this->line;
281
	}
282
283
	/**
284
	 * Returns the current context. Context is one of the Context::CONTEXT_* constants.
285
	 * 
286
	 * @return string
287
	 */
288 5
	public function getCurrentContext() {
289 5
		if ($this->contextStack->size() > 0) {
290 5
			return $this->contextStack->peek();
291
		}
292
293 3
		return self::CONTEXT_FILE;
294
	}
295
296
	/**
297
	 * Returns the current block context
298
	 * 
299
	 * @return Block
300
	 */
301
	public function getBlockContext() {
302
		return $this->block;
303
	}
304
305
	/**
306
	 * Returns the current group context
307
	 * 
308
	 * @return Group|null
309
	 */
310 2
	public function getGroupContext() {
311 2
		return $this->group;
312
	}
313
314 10
	private function peekBlock() {
315 10
		if ($this->blockStack->size() > 0) {
316 10
			return $this->blockStack->peek();
317
		}
318 10
		return new Block(null);
319
	}
320
321 10
	private function peekGroup() {
322 10
		if ($this->groupStack->size() > 0) {
323 10
			return $this->groupStack->peek();
324
		}
325 10
		return new Group();
326
	}
327
}
328