Completed
Branch master (486a28)
by Paweł
02:07
created

CodeReviewAnalyzer::processFile()   C

Complexity

Conditions 16
Paths 39

Size

Total Lines 61
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

Changes 8
Bugs 1 Features 5
Metric Value
cc 16
eloc 32
c 8
b 1
f 5
nc 39
nop 2
dl 0
loc 61
rs 6.2087
ccs 0
cts 39
cp 0
crap 272

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
class CodeReviewAnalyzer {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
3
4
	/**
5
	 * @var CodeReviewConfig
6
	 */
7
	protected $options;
8
9
	/**
10
	 * Function names seen as called
11
	 *
12
	 * @var array
13
	 */
14
	protected $calledFunctions = array();
15
16
	/**
17
	 * @var array
18
	 */
19
	protected $stats;
20
21
	/**
22
	 * @var integer
23
	 */
24
	protected $filesAnalyzed;
25
26
	/**
27
	 * @var string
28
	 */
29
	protected $maxVersion;
30
31
	/**
32
	 * Array of basic function names replacements
33
	 *
34
	 * @var array
35
	 */
36
	protected $instantReplacements;
37
38
	/**
39
	 * @var bool
40
	 */
41
	protected $fixProblems;
42
43
	/**
44
	 * @param CodeReviewConfig $options
45
	 */
46
	public function __construct(CodeReviewConfig $options = null) {
47
48
		if ($options === null) {
49
			$options = new CodeReviewConfig();
50
		}
51
		$this->options = $options;
52
53
		$this->maxVersion = $options->getMaxVersion();
54
		$this->fixProblems = $options->isFixProblemsEnabled();
55
	}
56
57
	/**
58
	 * @param string $subPath
59
	 * @param bool   $skipInactive
0 ignored issues
show
Bug introduced by
There is no parameter named $skipInactive. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
60
	 * @throws CodeReview_IOException
61
	 * @return CodeReviewFileFilterIterator
62
	 */
63
	public function getPhpFilesIterator($subPath = 'engine/') {
64
		$config = code_review::getConfig();
65
		$path = $config['path'] . $subPath;
66
		if (!file_exists($path)) {
67
			throw new CodeReview_IOException("Invalid subPath specified. $path does not exists!");
68
		}
69
		$i = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
70
		$i = new RecursiveIteratorIterator($i, RecursiveIteratorIterator::LEAVES_ONLY);
71
		$i = new RegexIterator($i, "/.*\.php/");
72
		$i = new CodeReviewFileFilterIterator($i, $config['path'], $this->options);
73
		return $i;
74
	}
75
76
	/**
77
	 * @return array
78
	 */
79
	public function analyze() {
80
81
		$options = $this->options;
82
83
		$i = $this->getPhpFilesIterator($options->getSubPath());
84
85
		$fixer = new CodeFixer();
86
		$this->instantReplacements = $fixer->getBasicFunctionRenames($this->maxVersion);
87
88
		$this->stats = array();
89
		$this->filesAnalyzed = 0;
90
91
		$functions = array();
92
		if ($options->isDeprecatedFunctionsTestEnabled()) {
93
			$functions = array_merge($functions, code_review::getDeprecatedFunctionsList($options->getMaxVersion()));
94
		}
95
		if ($options->isPrivateFunctionsTestEnabled()) {
96
			$functions = array_merge($functions, code_review::getPrivateFunctionsList());
97
		}
98
99
		foreach ($i as $filePath => $file) {
100
			if ($file instanceof SplFileInfo) {
101
				$result = $this->processFile($filePath, $functions);
102
				$this->filesAnalyzed++;
103
				if (!empty($result['problems'])) {
104
					$this->stats[$filePath] = $result;
105
				}
106
			}
107
		}
108
		return $this->stats;
109
	}
110
111
	/**
112
	 * @return string
113
	 */
114
	private function outputReportHeader() {
115
116
		$options = $this->options;
117
118
		$result = '';
119
120
		$result .= "Subpath selected <strong>" . $options->getSubPath() . "</strong>\n";
121
		$result .= "Max version: " . $options->getMaxVersion() . "\n";
122
		$result .= "Skipped inactive plugins: " . ($options->isSkipInactivePluginsEnabled() ? 'yes' : 'no') . "\n";
123
		$result .= "Search for deprecated functions usage: " . ($options->isDeprecatedFunctionsTestEnabled() ? 'yes' : 'no') . "\n";
124
		$result .= "Search for private functions usage: " . ($options->isPrivateFunctionsTestEnabled() ? 'yes' : 'no') . "\n";
125
		$result .= "Attempt to fix problems: " . ($options->isFixProblemsEnabled() ? 'yes' : 'no') . "\n";
126
127
		foreach (array('problems', 'fixes') as $type) {
128
			$total = 0;
129
			foreach ($this->stats as $items) {
130
				$total += count($items[$type]);
131
			}
132
			$result .= "Found $total $type in " . count($this->stats) . " files\n";
133
		}
134
135
		if ($this->filesAnalyzed === 0) {
136
			$result .= "*** No files were processed! *** Analysis input parameters did not resolve to any files.\n";
137
		} else {
138
			$result .= "Processed " . $this->filesAnalyzed . " files total\n";
139
		}
140
141
		return $result;
142
	}
143
144
	/**
145
	 * @return string
146
	 */
147
	private function ouptutUnusedFunctionsReport() {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
148
		//prepare unused functions report
149
		$functions = get_defined_functions();
150
		$functions = array_filter($functions['user'], 'strtolower');
151
		$calledFunctions = array_filter($this->calledFunctions, 'strtolower');
152
		$deprecatedFunctions = array_filter(array_keys(code_review::getDeprecatedFunctionsList($this->maxVersion)), 'strtolower');
153
		$functions = array_diff($functions, $calledFunctions, $deprecatedFunctions);
154
155
		foreach ($functions as $key => $function) {
156
			if (function_exists($function)) {
157
				$reflectionFunction = new ReflectionFunction($function);
158
				if (!$reflectionFunction->isInternal()) {
159
					continue;
160
				}
161
				unset($reflectionFunction);
162
			}
163
			unset($functions[$key]);
164
		}
165
		sort($functions);
166
167
		//unused functions report
168
		$result = "Not called but defined funcions:\n";
169
		$baseLenght = strlen(elgg_get_root_path());
170
		foreach (array_values($functions) as $functionName) {
171
			$reflectionFunction = new ReflectionFunction($functionName);
172
			$path = substr($reflectionFunction->getFileName(), $baseLenght);
173
			if (strpos($path, 'engine') !== 0) {
174
				continue;
175
			}
176
			$result .= "$functionName \t{$path}:{$reflectionFunction->getStartLine()}\n";
177
		}
178
		return $result;
179
	}
180
181
	/**
182
	 * @return string
183
	 */
184
	public function outputReport() {
185
186
		$options = $this->options;
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
187
188
		$result = $this->outputReportHeader();
189
190
		/*
191
		 * Full report
192
		 */
193
		foreach ($this->stats as $filePath => $items) {
194
			$result .= "\nIn file: " . $filePath . "\n";
195
196
			//problems
197
			foreach ($items['problems'] as $row) {
198
				list($data, $function, $line) = $row;
0 ignored issues
show
Unused Code introduced by
The assignment to $function is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
199
				$version = $data['version'];
0 ignored issues
show
Unused Code introduced by
$version is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
200
				$result .= "    " . (string)$data . "\n";
201
			}
202
203
			//fixes
204
			foreach ($items['fixes'] as $row) {
205
				list($before, $after, $line) = $row;
206
				$result .= "    Line $line:\tReplacing: '$before' with '$after'\n";
207
			}
208
		}
209
		
210
		$result .= "\n";
211
212
//		$result .= $this->ouptutUnusedFunctionsReport();
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
213
214
		$result .= "\n";
215
216
		return $result;
217
	}
218
219
	/**
220
	 * Find function calls and extract
221
	 *
222
	 * @param string $filePath
223
	 * @param array $functions
224
	 * @return array
225
	 */
226
	public function processFile($filePath, $functions) {
227
		$result = array(
228
			'problems' => array(),
229
			'fixes' => array(),
230
		);
231
		$phpTokens = new PhpFileParser($filePath);
232
		$changes = 0;
233
		foreach ($phpTokens as $key => $row) {
234
			// get non trivial tokens
235
			if (is_array($row)) {
236
				list($token, $functionName, $lineNumber) = $row;
237
				$originalFunctionName = $functionName;
238
239
				// prepare normalized version of function name for matching
240
				$functionName = strtolower($functionName);
241
//				if ($token == T_CONSTANT_ENCAPSED_STRING && function_exists(trim($functionName, '\'""'))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% 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...
242
//					$functionName = trim($functionName, '\'""');
243
//					if (!in_array($functionName, $this->calledFunctions)) {
244
//						$this->calledFunctions[] = $functionName;
245
//					}
246
//				}
247
248
				// check for function call
249
				if ($token == T_STRING
250
					&& !$phpTokens->isEqualToToken(T_OBJECT_OPERATOR, $key-1) //not method
251
					&& !$phpTokens->isEqualToToken(T_DOUBLE_COLON, $key-1) //not static method
252
					&& !$phpTokens->isEqualToToken(T_FUNCTION, $key-2) //not definition
253
				) {
254
					// mark function as called
255
					if (function_exists($functionName) && !in_array($functionName, $this->calledFunctions)) {
256
						$this->calledFunctions[] = $functionName;
257
					}
258
					// is it function we're looking for
259
					if (isset($functions[$functionName])) {
260
						$definingFunctionName = $phpTokens->getDefiningFunctionName($key);
261
262
						//we're skipping deprecated calls that are in deprecated function itself
263
						if (!$definingFunctionName || !isset($functions[strtolower($definingFunctionName)])) {
264
							$result['problems'][] = array($functions[$functionName], $originalFunctionName, $lineNumber);
265
						}
266
267
						//do instant replacement
268
						if ($this->fixProblems && isset($this->instantReplacements[$functionName])) {
269
							$phpTokens[$key] = array(T_STRING, $this->instantReplacements[$functionName]);
270
							$result['fixes'][] = array($originalFunctionName, $this->instantReplacements[$functionName], $lineNumber);
271
							$changes++;
272
						}
273
					}
274
				}
275
			}
276
		}
277
		if ($changes) {
278
			try {
279
				$phpTokens->exportPhp($filePath);
280
			} catch (CodeReview_IOException $e) {
281
				echo '*** Error: ' . $e->getMessage() . " ***\n";
282
			}
283
		}
284
		unset($phpTokens);
285
		return $result;
286
	}
287
}