Completed
Push — try/code-signature-diff ( 529505...b0bfae )
by
unknown
08:03
created

Difference_Missing::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Automattic\Jetpack\Analyzer;
4
5
use PhpParser\Error;
6
use PhpParser\NodeDumper;
7
use PhpParser\ParserFactory;
8
use PhpParser\Node;
9
use PhpParser\Node\Stmt\Function_;
10
use PhpParser\Node\Stmt\ClassMethod_;
11
use PhpParser\NodeTraverser;
12
use PhpParser\NodeVisitorAbstract;
13
14
// const STATE_NONE = 0;
15
// const STATE_CLASS_DECLARATION = 1;
16
17
const VIS_PUBLIC  = 0;
18
const VIS_PRIVATE = 1;
19
20
class Analyzer extends NodeVisitorAbstract {
21
	private $declarations;
22
	private $base_path;
23
	private $current_path;
24
	private $current_relative_path;
25
	private $current_class;
26
	private $parser;
27
28
	function __construct( $base_path ) {
29
		$this->parser       = ( new ParserFactory() )->create( ParserFactory::PREFER_PHP7 );
30
		$this->declarations = array();
31
		$this->base_path    = $this->slashit( $base_path );
32
	}
33
34
	private function slashit( $path ) {
35
		$path .= ( substr( $path, -1 ) == '/' ? '' : '/' );
36
		return $path;
37
	}
38
39
	protected function add_declaration( $declaration ) {
40
		$this->declarations[] = $declaration;
41
	}
42
43
	public function print_declarations() {
44
		echo $this->save_declarations( 'php://memory' );
45
	}
46
47
	/**
48
	 * Saves the declarations to a file and returns the file contents
49
	 */
50
	public function save_declarations( $file_path ) {
51
		$handle = fopen( $file_path, 'r+');
52
		foreach ( $this->declarations as $dec ) {
53
			fputcsv( $handle, $dec->to_csv_array() );
54
		}
55
		rewind( $handle );
56
		$contents = stream_get_contents( $handle );
57
		fclose( $handle );
58
		return $contents;
59
	}
60
61
	public function get_declarations() {
62
		return $this->declarations;
63
	}
64
65
	public function load_declarations( $file_path ) {
66
		$row = 1;
67
		if ( ( $handle = fopen( $file_path , "r" ) ) !== FALSE ) {
68
			while ( ( $data = fgetcsv( $handle, 1000, "," ) ) !== FALSE ) {
69
				$num = count( $data );
0 ignored issues
show
Unused Code introduced by
$num 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...
70
				list( $type, $file, $line, $class_name, $name, $static, $params_json ) = $data;
71
72
				switch( $type ) {
73
					case 'class':
74
						$this->add_declaration( new Class_Declaration( $file, $line, $class_name ) );
75
						break;
76
77
					case 'property':
78
						$this->add_declaration( new Class_Property_Declaration( $file, $line, $class_name, $name, $static ) );
79
						break;
80
81
					case 'method':
82
						$params = json_decode( $params_json );
83
						$declaration = new Class_Method_Declaration( $file, $line, $class_name, $name, $static );
84
						if ( is_array( $params ) ) {
85
							foreach( $params as $param ) {
86
								$declaration->add_param( $param->name, $param->default, $param->type, $param->byRef, $param->variadic );
87
							}
88
						}
89
90
						$this->add_declaration( $declaration );
91
92
						break;
93
				}
94
				$row++;
95
			}
96
			fclose($handle);
97
		}
98
	}
99
100
	public function scan() {
101
		$exclude = array( '.git', 'vendor', 'tests', 'docker', 'bin', 'scss', 'images', 'docs', 'languages', 'node_modules' );
102
		$filter  = function ( $file, $key, $iterator ) use ( $exclude ) {
103
			if ( $iterator->hasChildren() && ! in_array( $file->getFilename(), $exclude ) ) {
104
				return true;
105
			}
106
			return $file->isFile();
107
		};
108
109
		$inner_iterator = new \RecursiveDirectoryIterator( $this->base_path, \RecursiveDirectoryIterator::SKIP_DOTS );
110
111
		$iterator = new \RecursiveIteratorIterator(
112
			new \RecursiveCallbackFilterIterator( $inner_iterator, $filter )
113
		);
114
115
		$display = array( 'php' );
116
		foreach ( $iterator as $file ) {
117
			if ( in_array( strtolower( array_pop( explode( '.', $file ) ) ), $display ) ) {
0 ignored issues
show
Bug introduced by
explode('.', $file) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
118
				$this->file( $file );
119
			}
120
		}
121
	}
122
123
	public function file( $file_path ) {
124
		$this->current_path = $file_path;
125
		$this->current_relative_path = str_replace( $this->base_path, '', $file_path );
126
127
		$source             = file_get_contents( $file_path );
128
		try {
129
			$ast = $this->parser->parse( $source );
130
		} catch ( Error $error ) {
0 ignored issues
show
Bug introduced by
The class PhpParser\Error does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
131
			echo "Parse error: {$error->getMessage()}\n";
132
			return;
133
		}
134
135
		// $dumper = new NodeDumper;
136
		// echo $dumper->dump($ast) . "\n";
137
138
		$traverser = new NodeTraverser();
139
		$traverser->addVisitor( $this );
140
		$ast = $traverser->traverse( $ast );
0 ignored issues
show
Unused Code introduced by
$ast 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...
141
	}
142
143
	public function enterNode( Node $node ) {
144
		if ( $node instanceof Node\Stmt\Class_ ) {
0 ignored issues
show
Bug introduced by
The class PhpParser\Node\Stmt\Class_ does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
145
			$this->current_class = $node->name->name;
146
			$this->add_declaration( new Class_Declaration( $this->current_relative_path, $node->getLine(), $node->name->name ) );
147
		}
148
		if ( $node instanceof Node\Stmt\Property && $node->isPublic() ) {
0 ignored issues
show
Bug introduced by
The class PhpParser\Node\Stmt\Property does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
149
			$this->add_declaration( new Class_Property_Declaration( $this->current_relative_path, $node->getLine(), $this->current_class, $node->props[0]->name->name, $node->isStatic() ) );
150
		}
151
		if ( $node instanceof Node\Stmt\ClassMethod && $node->isPublic() ) {
0 ignored issues
show
Bug introduced by
The class PhpParser\Node\Stmt\ClassMethod does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
152
			$method = new Class_Method_Declaration( $this->current_relative_path, $node->getLine(), $this->current_class, $node->name->name, $node->isStatic() );
153
			foreach ( $node->getParams() as $param ) {
154
				$method->add_param( $param->var->name, $param->default, $param->type, $param->byRef, $param->variadic );
155
			}
156
			$this->add_declaration( $method );
157
		}
158
	}
159
160
	public function leaveNode( Node $node ) {
161
		if ( $node instanceof Node\Stmt\Class_ ) {
0 ignored issues
show
Bug introduced by
The class PhpParser\Node\Stmt\Class_ does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
162
			$this->current_class = null;
163
		}
164
	}
165
166
	public function find_differences( $analyzer ) {
167
		$differences = array();
168
		$total = 0;
169
		// for each declaration, see if it exists in the current analyzer's declarations
170
		// if not, add it to the list of differences - either as missing or different
171
		foreach( $analyzer->get_declarations() as $prev_declaration ) {
172
			$matched = false;
173
			foreach( $this->declarations as $declaration ) {
174
				if ( $prev_declaration->match( $declaration ) ) {
175
					$matched = true;
176
					break;
177
				}
178
			}
179
			if ( ! $matched ) {
180
				$differences[] = new Difference_Missing( $prev_declaration );
181
			}
182
			$total += 1;
183
		}
184
185
		echo "Total: $total\n";
186
		echo "Missing: " . count( $differences ) . "\n";
187
		return $differences;
188
	}
189
}
190
191
class Difference_Missing {
192
	public $declaration;
193
194
	function __construct( $declaration ) {
195
		$this->declaration = $declaration;
196
	}
197
198
	public function to_csv() {
199
		return 'missing,' . $this->declaration->type() . ',' . $this->declaration->display_name();
200
	}
201
}
202
203
/*
204
class Difference_Params {
205
	public $declaration;
206
207
	function __construct( $declaration ) {
208
		$this->declaration = $declaration;
209
	}
210
211
	public function to_csv() {
212
		return 'params,' . implode( ',', $this->declaration->to_csv_array() );
213
	}
214
}
215
*/
216
217
abstract class Declaration {
218
	public $path;
219
	public $line;
220
221
	function __construct( $path, $line ) {
222
		$this->path = $path;
223
		$this->line = $line;
224
	}
225
226
	function match( $other ) {
227
		return get_class( $other ) === get_class( $this )
228
			&& $other->name === $this->name
0 ignored issues
show
Bug introduced by
The property name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
229
			&& $other->path === $this->path;
230
	}
231
232
	abstract function type();
233
	// e.g. Jetpack::get_file_url_for_environment()
234
	abstract function display_name();
235
}
236
237
class Class_Declaration extends Declaration {
238
	public $class_name;
239
240
	function __construct( $path, $line, $class_name ) {
241
		$this->class_name = $class_name;
242
		parent::__construct( $path, $line );
243
	}
244
245
	function to_csv_array() {
246
		return array(
247
			$this->type(),
248
			$this->path,
249
			$this->line,
250
			$this->class_name
251
		);
252
	}
253
254
	function type() {
255
		return 'class';
256
	}
257
258
	function display_name() {
259
		return $this->class_name;
260
	}
261
}
262
263
/**
264
 * We only log public class methods, whether they are static, and their parameters
265
 */
266
class Class_Method_Declaration extends Declaration {
267
	public $class_name;
268
	public $name;
269
	public $params;
270
	public $static;
271
272 View Code Duplication
	function __construct( $path, $line, $class_name, $name, $static ) {
273
		$this->class_name = $class_name;
274
		$this->name = $name;
275
		$this->params = array();
276
		$this->static = $static;
277
		parent::__construct( $path, $line );
278
	}
279
280
	// TODO: parse "default" into comparable string form?
281
	function add_param( $name, $default, $type, $byRef, $variadic ) {
282
		$this->params[] = (object) compact( 'name', 'default', 'type', 'byRef', 'variadic' );
283
	}
284
285 View Code Duplication
	function to_csv_array() {
286
		return array(
287
			$this->type(),
288
			$this->path,
289
			$this->line,
290
			$this->class_name,
291
			$this->name,
292
			$this->static,
293
			json_encode( $this->params )
294
		);
295
	}
296
297
	function type() {
298
		return 'method';
299
	}
300
301
	function display_name() {
302
		$sep = $this->static ? '::' : '->';
303
		return $this->class_name . $sep . $this->name;
304
	}
305
}
306
307
/**
308
 * We only log public class variables
309
 */
310
class Class_Property_Declaration extends Declaration {
311
	public $class_name;
312
	public $name;
313
	public $static;
314
315 View Code Duplication
	function __construct( $path, $line, $class_name, $name, $static ) {
316
		$this->class_name = $class_name;
317
		$this->name = $name;
318
		$this->static = $static;
319
		parent::__construct( $path, $line );
320
	}
321
322 View Code Duplication
	function to_csv_array() {
323
		return array(
324
			$this->type(),
325
			$this->path,
326
			$this->line,
327
			$this->class_name,
328
			$this->name,
329
			$this->static,
330
			''
331
		);
332
	}
333
334
	function type() {
335
		return 'property';
336
	}
337
338
	function display_name() {
339
		$sep = $this->static ? '::$' : '->';
340
		return $this->class_name . $sep . $this->name;
341
	}
342
}
343