fixSpacesFromNamespaceToUseStatements()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 7
CRAP Score 4.1755

Importance

Changes 0
Metric Value
dl 16
loc 16
ccs 7
cts 9
cp 0.7778
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 2
crap 4.1755
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 PHP_CodeSniffer_Sniff;
14
use ZenifyCodingStandard\Helper\Whitespace\ClassMetrics;
15
use ZenifyCodingStandard\Helper\Whitespace\WhitespaceFinder;
16
17
18
/**
19
 * Rules:
20
 * - There must be x empty line(s) after the namespace declaration or y empty line(s) followed by use statement.
21
 */
22
final class NamespaceDeclarationSniff implements PHP_CodeSniffer_Sniff
23
{
24
25
	/**
26
	 * @var string
27
	 */
28
	const NAME = 'ZenifyCodingStandard.Namespaces.NamespaceDeclaration';
29
30
	/**
31
	 * @var int
32
	 */
33
	public $emptyLinesAfterNamespace = 2;
34
35
	/**
36
	 * @var int
37
	 */
38
	private $emptyLinesBeforeUseStatement = 1;
39
40
	/**
41
	 * @var PHP_CodeSniffer_File
42
	 */
43
	private $file;
44
45
	/**
46
	 * @var int
47
	 */
48
	private $position;
49
50
	/**
51
	 * @var array[]
52
	 */
53
	private $tokens;
54
55
	/**
56
	 * @var ClassMetrics
57
	 */
58
	private $classMetrics;
59
60
61
	/**
62
	 * @return int[]
63
	 */
64 2
	public function register() : array
65
	{
66 2
		return [T_NAMESPACE];
67
	}
68
69
70
	/**
71
	 * @param PHP_CodeSniffer_File $file
72
	 * @param int $position
73
	 */
74 2
	public function process(PHP_CodeSniffer_File $file, $position)
75
	{
76 2
		$classPosition = $file->findNext([T_CLASS, T_TRAIT, T_INTERFACE], $position);
77 2
		if ( ! $classPosition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classPosition of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
78
			// there is no class, nothing to see here
79
			return;
80
		}
81
82 2
		$this->file = $file;
83 2
		$this->position = $position;
84 2
		$this->tokens = $file->getTokens();
85
86
		// Fix type
87 2
		$this->emptyLinesAfterNamespace = (int) $this->emptyLinesAfterNamespace;
88 2
		$this->emptyLinesBeforeUseStatement = (int) $this->emptyLinesBeforeUseStatement;
89
90
		// prepare class metrics class
91 2
		$this->classMetrics = new ClassMetrics($file, $classPosition);
92
93
		$lineDistanceBetweenNamespaceAndFirstUseStatement =
94 2
			$this->classMetrics->getLineDistanceBetweenNamespaceAndFirstUseStatement();
95
		$lineDistanceBetweenClassAndNamespace =
96 2
			$this->classMetrics->getLineDistanceBetweenClassAndNamespace();
97
98 2
		if ($lineDistanceBetweenNamespaceAndFirstUseStatement) {
99 2
			$this->processWithUseStatement($lineDistanceBetweenNamespaceAndFirstUseStatement);
100
101
		} else {
102 2
			$this->processWithoutUseStatement($lineDistanceBetweenClassAndNamespace);
0 ignored issues
show
Bug introduced by
It seems like $lineDistanceBetweenClassAndNamespace defined by $this->classMetrics->get...weenClassAndNamespace() on line 96 can also be of type double or false; however, ZenifyCodingStandard\Sni...ssWithoutUseStatement() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
103
		}
104 2
	}
105
106
107 2
	private function processWithoutUseStatement(int $linesToNextClass)
108
	{
109 2
		if ($linesToNextClass) {
110 2
			if ($linesToNextClass !== $this->emptyLinesAfterNamespace) {
111 2
				$errorMessage = sprintf(
112 2
					'There should be %s empty line(s) after the namespace declaration; %s found',
113 2
					$this->emptyLinesAfterNamespace,
114
					$linesToNextClass
115
				);
116
117 2
				$fix = $this->file->addFixableError($errorMessage, $this->position);
118 2
				if ($fix) {
119 1
					$this->fixSpacesFromNamespaceToClass($this->position, $linesToNextClass);
120
				}
121
			}
122
		}
123 2
	}
124
125
126 2
	private function processWithUseStatement(int $linesToNextUse)
127
	{
128 2
		if ($linesToNextUse !== $this->emptyLinesBeforeUseStatement) {
129 2
			$errorMessage = sprintf(
130 2
				'There should be %s empty line(s) from namespace to use statement; %s found',
131 2
				$this->emptyLinesBeforeUseStatement,
132
				$linesToNextUse
133
			);
134
135 2
			$fix = $this->file->addFixableError($errorMessage, $this->position);
136 2
			if ($fix) {
137 1
				$this->fixSpacesFromNamespaceToUseStatements($this->position, $linesToNextUse);
138
			}
139
		}
140
141 2
		$linesToNextClass = $this->classMetrics->getLineDistanceBetweenClassAndLastUseStatement();
142 2
		if ($linesToNextClass !== $this->emptyLinesAfterNamespace) {
143 1
			$errorMessage = sprintf(
144 1
				'There should be %s empty line(s) between last use and class; %s found',
145 1
				$this->emptyLinesAfterNamespace,
146
				$linesToNextClass
147
			);
148
149 1
			$fix = $this->file->addFixableError($errorMessage, $this->position);
150 1
			if ($fix) {
151 1
				$this->fixSpacesFromUseStatementToClass(
152 1
					$this->classMetrics->getLastUseStatementPosition(),
0 ignored issues
show
Bug introduced by
It seems like $this->classMetrics->get...tUseStatementPosition() targeting ZenifyCodingStandard\Hel...tUseStatementPosition() can also be of type boolean; however, ZenifyCodingStandard\Sni...omUseStatementToClass() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
153
					$linesToNextClass
0 ignored issues
show
Bug introduced by
It seems like $linesToNextClass defined by $this->classMetrics->get...ssAndLastUseStatement() on line 141 can also be of type double or false; however, ZenifyCodingStandard\Sni...omUseStatementToClass() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
154
				);
155
			}
156
		}
157 2
	}
158
159
160 1 View Code Duplication
	private function fixSpacesFromNamespaceToUseStatements(int $position, int $linesToNextUse)
161
	{
162 1
		$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $position);
163
164 1
		if ($linesToNextUse < $this->emptyLinesBeforeUseStatement) {
165
			for ($i = $linesToNextUse; $i < $this->emptyLinesBeforeUseStatement; $i++) {
166
				$this->file->fixer->addContent($nextLinePosition, PHP_EOL);
167
			}
168
169
		} else {
170 1
			for ($i = $linesToNextUse; $i > $this->emptyLinesBeforeUseStatement; $i--) {
171 1
				$this->file->fixer->replaceToken($nextLinePosition, '');
172 1
				$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $nextLinePosition);
173
			}
174
		}
175 1
	}
176
177
178 1 View Code Duplication
	private function fixSpacesFromNamespaceToClass(int $position, int $linesToClass)
179
	{
180 1
		$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $position);
181 1
		if ($linesToClass < $this->emptyLinesAfterNamespace) {
182 1
			for ($i = $linesToClass; $i < $this->emptyLinesAfterNamespace; $i++) {
183 1
				$this->file->fixer->addContent($nextLinePosition, PHP_EOL);
184
			}
185
186
		} else {
187 1
			for ($i = $linesToClass; $i > $this->emptyLinesAfterNamespace; $i--) {
188 1
				$this->file->fixer->replaceToken($nextLinePosition, '');
189 1
				$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $nextLinePosition);
190
			}
191
		}
192 1
	}
193
194
195 1 View Code Duplication
	private function fixSpacesFromUseStatementToClass(int $position, int $linesToClass)
196
	{
197 1
		if ($linesToClass < $this->emptyLinesAfterNamespace) {
198
			for ($i = $linesToClass; $i < $this->emptyLinesAfterNamespace; $i++) {
199
				$this->file->fixer->addContent($position, PHP_EOL);
200
			}
201
202
		} else {
203 1
			$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $position);
204 1
			for ($i = $linesToClass; $i > $this->emptyLinesAfterNamespace; $i--) {
205 1
				$this->file->fixer->replaceToken($nextLinePosition, '');
206 1
				$nextLinePosition = WhitespaceFinder::findNextEmptyLinePosition($this->file, $nextLinePosition);
207
			}
208
		}
209 1
	}
210
211
}
212