Skip to content

Commit f6537af

Browse files
committed
Use regex instead of PHPParser to speed up class scanning drastically
1 parent c8b5a0d commit f6537af

File tree

5 files changed

+101
-32
lines changed

5 files changed

+101
-32
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2011-2018 ResearchGate GmbH
1+
Copyright (c) ResearchGate GmbH
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"require": {
1212
"php": "^7.1",
1313
"symfony/console" : "^2.1|^3.0|^4.0|^5.0",
14-
"nikic/php-parser" : "^4.0",
1514
"phpunit/php-timer": "^2.0"
1615
},
1716
"autoload" : {

src/ClassScanner.php

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@
1010

1111
namespace rg\tools\phpnsc;
1212

13-
use PhpParser\Error;
14-
use PhpParser\Node\Stmt\Namespace_;
15-
use PhpParser\ParserFactory;
16-
1713
class ClassScanner
1814
{
1915
/**
@@ -55,31 +51,17 @@ public function __construct(FilesystemAccess $filesystem, Output $output)
5551
*/
5652
public function parseFilesForClassesAndInterfaces($files, $root, $namespaceVendor)
5753
{
58-
$parserFactory = new ParserFactory();
59-
$parser = $parserFactory->create(ParserFactory::PREFER_PHP7);
60-
6154
$progressbar = new Progressbar($this->output, count($files));
6255
foreach ($files as $file) {
6356
$namespace = (string) new NamespaceString($namespaceVendor, $root, $file);
6457
$originalFileContent = $this->filesystem->getFile($file);
6558

66-
try {
67-
$stmts = $parser->parse($originalFileContent);
68-
$firstStatement = $stmts[0];
69-
if ($firstStatement instanceof Namespace_) {
70-
$namespaceOfFile = implode('\\', $firstStatement->name->parts);
71-
if ($namespace !== $namespaceOfFile) {
72-
$this->foundError = true;
73-
$this->output->addError('Namespace does not match folder structure, got '.$namespaceOfFile.' expected '.$namespace, $file, $firstStatement->getLine());
74-
}
59+
if (preg_match('#^(?:<\\?php\s+)?namespace\s+([a-zA-Z0-9_\\\\]+?)(?:;|\s+\{?\W)$#sm', $originalFileContent, $matches)) {
60+
$namespaceOfFile = $matches[1];
61+
if ($namespace !== $namespaceOfFile) {
62+
$this->foundError = true;
63+
$this->output->addError('Namespace does not match folder structure, got '.$namespaceOfFile.' expected '.$namespace, $file, 2);
7564
}
76-
} catch (Error $e) {
77-
$this->foundError = true;
78-
$this->output->addError(
79-
'Parse Error: '.$e->getMessage(),
80-
$file,
81-
1
82-
);
8365
}
8466
$fileContent = $this->cleanContent($originalFileContent);
8567
$this->parseDefinedEntities($file, $namespace, $fileContent, $originalFileContent);
@@ -113,10 +95,11 @@ private function cleanContent($fileContent)
11395
preg_match_all($pattern, $fileContent, $matches, PREG_OFFSET_CAPTURE);
11496
if (isset($matches[1])) {
11597
foreach ($matches[1] as $match) {
98+
$matchLength = strlen($match[0]);
11699
$fileContent =
117100
substr($fileContent, 0, $match[1]).
118-
$getWhitespaces(strlen($match[0])).
119-
substr($fileContent, $match[1] + strlen($match[0]));
101+
$getWhitespaces($matchLength).
102+
substr($fileContent, $match[1] + $matchLength);
120103
}
121104
}
122105

src/NamespaceDependencyChecker.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ private function analyzeFile($file)
7878
$entitiesUsedInFile = $this->classScanner->getUsedEntities($file);
7979

8080
$fileNamespace = (string) new NamespaceString($this->namespaceVendor, $this->root, $file);
81+
$fileNamespaceLength = strlen($fileNamespace) + 1;
8182
foreach ($entitiesUsedInFile as $usedEntity => $lines) {
8283
// we have a fully qualified name, so we do not need any use statements
8384
if (substr($usedEntity, 0, 1) === '\\') {
@@ -102,7 +103,7 @@ private function analyzeFile($file)
102103
continue 2;
103104
}
104105
if (strpos($usedEntityNamespaceT, $fileNamespace) === 0) {
105-
$usedEntityNamespaceT = substr($usedEntityNamespaceT, strlen($fileNamespace) + 1);
106+
$usedEntityNamespaceT = substr($usedEntityNamespaceT, $fileNamespaceLength);
106107
if (preg_match('/\Wuse\s+\\\?'.str_replace('\\', '\\\\', $usedEntityNamespaceT).';/', $fileContent)) {
107108
continue 2;
108109
}
@@ -123,13 +124,13 @@ private function analyzeFile($file)
123124
if (preg_match('/\Wuse\s+\\\?'.str_replace('\\', '\\\\', $usedEntityNamespaceT).';/', $fileContent)) {
124125
continue 2;
125126
}
126-
127+
127128
}
128129
$errorMessage = 'Class '.$usedEntity.' (fully qualified: '.$usedEntityNamespace.'\\'.$simpleName.') was referenced relatively but has no matching use statement';
129130
} else {
130131
$errorMessage = 'Class '.$usedEntity.' was referenced relatively but not defined';
131132
}
132-
133+
133134
$regexUsedEntity = str_replace('\\', '\\\\', $usedEntity);
134135
if (preg_match('/\Wuse\s+(?:\\\?|[a-zA-Z0-9_\\\]+\\\\)'.$regexUsedEntity.';/', $fileContent)) {
135136
continue;

test/rg/tools/phpnsc/NamespaceDependencyCheckerTest.php

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ protected function setUp() {
5050
public function testModifyFiles() {
5151
$this->dependencyChecker->analyze($this->files);
5252
$expected = [
53+
[
54+
'Namespace does not match folder structure, got vendor\testnamespaceTwo expected vendor\testnamespaceInvalid',
55+
'/root/folder/testnamespaceInvalid/ClassX.php',
56+
2,
57+
],
58+
[
59+
'Namespace does not match folder structure, got vendor\testnamespaceTwo expected vendor\testnamespaceInvalid',
60+
'/root/folder/testnamespaceInvalid/ClassY.php',
61+
2,
62+
],
63+
[
64+
'Namespace does not match folder structure, got vendor\testnamespaceTwo expected vendor\testnamespaceInvalid',
65+
'/root/folder/testnamespaceInvalid/ClassZ.php',
66+
2,
67+
],
68+
[
69+
'Namespace does not match folder structure, got vendor\testnamespaceTwo expected vendor\testnamespaceInvalid',
70+
'/root/folder/testnamespaceInvalid/ClassZZ.php',
71+
2,
72+
],
5373
[
5474
'Class TestException was referenced relatively but not defined',
5575
'/root/folder/testnamespace/ClassOne.php',
@@ -78,6 +98,7 @@ public function testModifyFiles() {
7898
class DependencyCheckerOutputMock implements Output
7999
{
80100
public $errors = array();
101+
public $warnings = array();
81102

82103
public function __construct(OutputInterface $output, $parameter = null) {
83104

@@ -87,6 +108,11 @@ public function addError($description, $file, $line) {
87108
$description, $file, $line,
88109
);
89110
}
111+
public function addWarning($description, $file, $line) {
112+
$this->warnings[] = array(
113+
$description, $file, $line,
114+
);
115+
}
90116
public function printAll() {
91117

92118
}
@@ -119,11 +145,11 @@ public function test(\\OutOfNamespace $foo, OtherNamespace $bar, GlobalAlias $an
119145
} catch(TestException $e) {
120146
}
121147
}
122-
148+
123149
private function testVoidAsReturnType(): void {
124150
// this declaration should work fine
125151
}
126-
152+
127153
private function testVoidAsArgument(void $foo) {
128154
// this should not be allowed
129155
}
@@ -175,6 +201,66 @@ public function test();
175201
interface InterfaceB {
176202
public function test();
177203
}
204+
',
205+
'/root/folder/testnamespaceInvalid/ClassX.php' =>
206+
'<?php
207+
namespace vendor\testnamespaceTwo;
208+
209+
class ClassX {
210+
public function test() {}
211+
}
212+
',
213+
'/root/folder/testnamespaceInvalid/ClassY.php' =>
214+
'<?php
215+
namespace vendor\testnamespaceTwo {
216+
217+
class ClassY {
218+
public function test() {}
219+
}
220+
}
221+
',
222+
'/root/folder/testnamespaceInvalid/ClassZ.php' =>
223+
'<?php
224+
namespace vendor\testnamespaceTwo
225+
{
226+
class ClassZ {
227+
public function test() {}
228+
}
229+
}
230+
',
231+
'/root/folder/testnamespaceInvalid/ClassZZ.php' =>
232+
'<?php namespace vendor\testnamespaceTwo
233+
{
234+
class ClassZZ {
235+
public function test() {}
236+
}
237+
}
238+
',
239+
'/root/folder/testnamespaceValid/ClassX.php' =>
240+
'<?php
241+
namespace vendor\testnamespaceValid;
242+
243+
class ClassX {
244+
public function test() {}
245+
}
246+
',
247+
'/root/folder/testnamespaceValid/ClassY.php' =>
248+
'<?php
249+
namespace vendor\testnamespaceValid {
250+
251+
class ClassY {
252+
public function test() {}
253+
}
254+
}
255+
',
256+
'/root/folder/testnamespaceValid/ClassZ.php' =>
257+
'<?php
258+
namespace vendor\testnamespaceValid
259+
{
260+
class ClassZ {
261+
public function test() {}
262+
}
263+
}
178264
',
179265
);
180266
public function getFile($filename) {

0 commit comments

Comments
 (0)