UseDeclarationSniff   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 81.36%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 2
dl 0
loc 133
ccs 48
cts 59
cp 0.8136
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 4 1
B process() 0 28 5
A shouldIgnoreUse() 0 17 3
A checkIfSingleSpaceAfterUseKeyword() 0 8 2
A checkIfOneUseDeclarationPerStatement() 0 9 2
A checkIfUseComesAfterNamespaceDeclaration() 0 11 3
A checkBlankLineAfterLastUseStatement() 0 16 3
1
<?php
2
3
declare(strict_types = 1);
4
5
/*
6
 * This file is part of Zenify
7
 * Copyright (c) 2012 Tomas Votruba (http://tomasvotruba.cz)
8
 */
9
10
namespace ZenifyCodingStandard\Sniffs\Namespaces;
11
12
use PHP_CodeSniffer_File;
13
use PSR2_Sniffs_Namespaces_UseDeclarationSniff;
14
15
16
/**
17
 * Rules:
18
 * - There must be one USE keyword per declaration
19
 * - USE declarations must go after the first namespace declaration
20
 * - There must be 2 blank line(s) after the last USE statement
21
 */
22
final class UseDeclarationSniff extends PSR2_Sniffs_Namespaces_UseDeclarationSniff
23
{
24
25
	/**
26
	 * @var string
27
	 */
28
	const NAME = 'ZenifyCodingStandard.Namespaces.UseDeclaration';
29
30
	/**
31
	 * @var int
32
	 */
33
	public $blankLinesAfterUseStatement = 2;
34
35
36
	/**
37
	 * @return int[]
38
	 */
39 1
	public function register() : array
40
	{
41 1
		return [T_USE];
42
	}
43
44
45
	/**
46
	 * @param PHP_CodeSniffer_File $file
47
	 * @param int $position
48
	 */
49 1
	public function process(PHP_CodeSniffer_File $file, $position)
50
	{
51
		// Fix types
52 1
		$this->blankLinesAfterUseStatement = (int) $this->blankLinesAfterUseStatement;
53
54 1
		if ($this->shouldIgnoreUse($file, $position) === TRUE) {
55
			return;
56
		}
57
58 1
		$this->checkIfSingleSpaceAfterUseKeyword($file, $position);
59 1
		$this->checkIfOneUseDeclarationPerStatement($file, $position);
60 1
		$this->checkIfUseComesAfterNamespaceDeclaration($file, $position);
61
62
		// Only interested in the last USE statement from here onwards.
63 1
		$nextUse = $file->findNext(T_USE, ($position + 1));
64 1
		while ($this->shouldIgnoreUse($file, $nextUse) === TRUE) {
65
			$nextUse = $file->findNext(T_USE, ($nextUse + 1));
66
			if ($nextUse === FALSE) {
67
				break;
68
			}
69
		}
70
71 1
		if ($nextUse !== FALSE) {
72 1
			return;
73
		}
74
75 1
		$this->checkBlankLineAfterLastUseStatement($file, $position);
76 1
	}
77
78
79
	/**
80
	 * Check if this use statement is part of the namespace block.
81
	 * @param PHP_CodeSniffer_File $file
82
	 * @param int|bool $position
83
	 */
84 1
	private function shouldIgnoreUse(PHP_CodeSniffer_File $file, $position) : bool
85
	{
86 1
		$tokens = $file->getTokens();
87
88
		// Ignore USE keywords inside closures.
89 1
		$next = $file->findNext(T_WHITESPACE, ($position + 1), NULL, TRUE);
90 1
		if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
91
			return TRUE;
92
		}
93
94
		// Ignore USE keywords for traits.
95 1
		if ($file->hasCondition($position, [T_CLASS, T_TRAIT]) === TRUE) {
96
			return TRUE;
97
		}
98
99 1
		return FALSE;
100
	}
101
102
103 1
	private function checkIfSingleSpaceAfterUseKeyword(PHP_CodeSniffer_File $file, int $position)
104
	{
105 1
		$tokens = $file->getTokens();
106 1
		if ($tokens[($position + 1)]['content'] !== ' ') {
107
			$error = 'There must be a single space after the USE keyword';
108
			$file->addError($error, $position, 'SpaceAfterUse');
109
		}
110 1
	}
111
112
113 1
	private function checkIfOneUseDeclarationPerStatement(PHP_CodeSniffer_File $file, int $position)
114
	{
115 1
		$tokens = $file->getTokens();
116 1
		$next = $file->findNext([T_COMMA, T_SEMICOLON], ($position + 1));
117 1
		if ($tokens[$next]['code'] === T_COMMA) {
118 1
			$error = 'There must be one USE keyword per declaration';
119 1
			$file->addError($error, $position, 'MultipleDeclarations');
120
		}
121 1
	}
122
123
124 1
	private function checkIfUseComesAfterNamespaceDeclaration(PHP_CodeSniffer_File $file, int $position)
125
	{
126 1
		$prev = $file->findPrevious(T_NAMESPACE, ($position - 1));
127 1
		if ($prev !== FALSE) {
128 1
			$first = $file->findNext(T_NAMESPACE, 1);
129 1
			if ($prev !== $first) {
130
				$error = 'USE declarations must go after the first namespace declaration';
131
				$file->addError($error, $position, 'UseAfterNamespace');
132
			}
133
		}
134 1
	}
135
136
137 1
	private function checkBlankLineAfterLastUseStatement(PHP_CodeSniffer_File $file, int $position)
138
	{
139 1
		$tokens = $file->getTokens();
140 1
		$end = $file->findNext(T_SEMICOLON, ($position + 1));
141 1
		$next = $file->findNext(T_WHITESPACE, ($end + 1), NULL, TRUE);
142 1
		$diff = ($tokens[$next]['line'] - $tokens[$end]['line'] - 1);
143 1
		if ($diff !== (int) $this->blankLinesAfterUseStatement) {
144 1
			if ($diff < 0) {
145
				$diff = 0;
146
			}
147
148 1
			$error = 'There must be %s blank line(s) after the last USE statement; %s found.';
149 1
			$data = [$this->blankLinesAfterUseStatement, $diff];
150 1
			$file->addError($error, $position, 'SpaceAfterLastUse', $data);
151
		}
152 1
	}
153
154
}
155