Skip to content

Commit 192a245

Browse files
committed
Namespace check
1 parent 83d3dc2 commit 192a245

File tree

7 files changed

+145
-21
lines changed

7 files changed

+145
-21
lines changed

.githooks/pre-commit

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
3+
PHP_CS_FIXER="vendor/bin/php-cs-fixer"
4+
PSALM="vendor/bin/psalm"
5+
6+
if [ ! -x $PHP_CS_FIXER ]; then
7+
echo ""
8+
echo "php-cs-fixer not found. Try:"
9+
echo ""
10+
echo " composer install"
11+
echo ""
12+
exit 1
13+
fi
14+
15+
if [ ! -x $PSALM ]; then
16+
echo ""
17+
echo "vimeo/psalm not found. Try:"
18+
echo ""
19+
echo " composer install"
20+
echo ""
21+
exit 1
22+
fi
23+
24+
FILES=`git status --porcelain | grep -E '^[AM] +(src|tests).*\.php$' | cut -c 4- | tr '\n' ' '`
25+
if [ -z "$FILES" ]; then
26+
echo "No php files found in commit."
27+
else
28+
output=`php -l ${FILES}`
29+
OUT=$?
30+
if [ $OUT != '0' ]; then
31+
echo "$output"
32+
echo ""
33+
echo "Please correct and recommit"
34+
35+
exit 1
36+
fi
37+
38+
$PHP_CS_FIXER fix --config=.php_cs.dist ${FILES} >/dev/null 2>&1
39+
git add ${FILES}
40+
41+
# output=`$PSALM --report=psalmCommitErrors.txt --config=psalmCommit.xml ${FILES} >/dev/null 2>&1`
42+
# OUT=$?
43+
# if [ $OUT != '0' ]; then
44+
# echo "PSALM Failed with the following errors:"
45+
# echo ""
46+
# cat psalmCommitErrors.txt
47+
# echo ""
48+
# echo "Please correct and recommit"
49+
#
50+
# exit 1
51+
# fi
52+
fi

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ language: php
33
php:
44
- 7.3
55
- 7.4
6+
- 8.0
67

78
before_script:
89
- wget http://getcomposer.org/composer.phar

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ Use **addSkipDirectory** to add simple case insensitive file matching to skip sp
4949
## Autoloading
5050
You must make sure autoloading is correctly configured for all classes. This means you can't pass references to classes that will not resolve correctly in your source. Use **addSkipDirectory** if you have test code that may not validate correctly.
5151

52+
## Namespace Testing
53+
The **assertValidPHPFile** and **assertValidPHPDirectory** asserts will test for the proper namespace in the file path (for PSR-0 autoloading and fully pathed PSR-4 autoloading), but you can turn off namespace testing with **skipNamespaceTesting** or exclude a specific namespace tests with **addSkipNamespace**.
54+
5255
## PHP Version
5356
While this library only supports PHP 7.1 or higher, you can create a project and point it to PHP 5.2 or higher. The default is to prefer PHP 7 code, but to prefer or only parse PHP 5, configure phpunit.xml(.dist) with
5457

composer.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@
1212
}
1313
],
1414
"require": {
15-
"php": ">=7.1",
16-
"nikic/php-parser": "^4.8",
17-
"phpunit/phpunit": ">=7.0",
18-
"roave/you-are-using-it-wrong": "^1.8"
15+
"php": ">=7.3",
16+
"nikic/php-parser": ">=4.10"
1917
},
2018
"autoload": {
2119
"psr-0": {
2220
"PHPFUI": "src/"
2321
}
2422
},
2523
"require-dev": {
26-
"roave/security-advisories": "dev-latest"
24+
"phpunit/phpunit": ">=9.0",
25+
"roave/security-advisories": "dev-latest",
26+
"vimeo/psalm": "^4.7",
27+
"friendsofphp/php-cs-fixer": "^2.18",
28+
"symfony/translation-contracts": "^2.3"
2729
}
2830
}

src/PHPFUI/PHPUnitSyntaxCoverage/Extensions.php

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,17 @@ public function enterNode(\PhpParser\Node $node) : void
2424
{
2525
$this->currentNamespace = \implode('\\', $node->name->parts);
2626
}
27-
elseif ($node instanceof \PhpParser\Node\Stmt\Class_)
27+
elseif ($node instanceof \PhpParser\Node\Stmt\Class_ && $node->name)
2828
{
2929
$this->classes[] = $this->currentNamespace ? $this->currentNamespace . '\\' . $node->name->name : $node->name->name;
3030
}
3131
}
3232

33+
public function getNamespace() : string
34+
{
35+
return $this->currentNamespace;
36+
}
37+
3338
public function getClasses() : array
3439
{
3540
return $this->classes;
@@ -42,12 +47,32 @@ class Extensions extends \PHPUnit\Framework\TestCase implements \PHPUnit\Runner\
4247

4348
private $skipDirectories = [];
4449

50+
private $skipNamespaces = [];
51+
52+
private $skipNamespaceTest = false;
53+
54+
private $traverser;
55+
56+
private $classFinder;
57+
4558
public static function setUpBeforeClass() : void
4659
{
4760
$factory = new \PhpParser\ParserFactory();
4861
self::$parser = $factory->create($_ENV[__CLASS__ . '_parser_type'] ?? \PhpParser\ParserFactory::PREFER_PHP7);
4962
}
5063

64+
protected function setUp() : void
65+
{
66+
$this->traverser = new \PhpParser\NodeTraverser();
67+
$this->classFinder = new ClassFinder();
68+
}
69+
70+
protected function tearDown() : void
71+
{
72+
$this->traverser = null;
73+
$this->classFinder = null;
74+
}
75+
5176
/**
5277
* Assert a string containing valid PHP will parse.
5378
*
@@ -68,13 +93,11 @@ public function assertValidPHP(string $code, string $message = '') : void
6893

6994
$this->assertNotEmpty($ast, 'Empty Abstract Syntax tree. ' . $message);
7095

71-
$traverser = new \PhpParser\NodeTraverser();
72-
$classFinder = new ClassFinder();
73-
$traverser->addVisitor($classFinder);
96+
$this->traverser->addVisitor($this->classFinder);
7497

75-
$traverser->traverse($ast);
98+
$this->traverser->traverse($ast);
7699

77-
foreach ($classFinder->getClasses() as $class)
100+
foreach ($this->classFinder->getClasses() as $class)
78101
{
79102
try
80103
{
@@ -101,6 +124,28 @@ public function addSkipDirectory(string $directory) : self
101124
return $this;
102125
}
103126

127+
/**
128+
* Skip namespace testing
129+
*/
130+
public function skipNamespaceTesting() : self
131+
{
132+
$this->skipNamespaceTest = true;
133+
134+
return $this;
135+
}
136+
137+
/**
138+
* Exclude namespace from namespace testing
139+
*
140+
* You can add multiple namespaces to skip.
141+
*/
142+
public function addSkipNamespace(string $namespace) : self
143+
{
144+
$this->skipNamespaces[] = $namespace;
145+
146+
return $this;
147+
}
148+
104149
/**
105150
* Validate all files in a directory. Recursive and only looks at .php files by default.
106151
*/
@@ -143,7 +188,9 @@ public function assertValidPHPDirectory(string $directory, string $message = '',
143188

144189
if (! $skip)
145190
{
191+
$this->setup();
146192
$this->assertValidPHPFile($file, $message . "\nFile: " . $file);
193+
$this->tearDown();
147194
}
148195
}
149196
}
@@ -160,5 +207,16 @@ public function assertValidPHPFile(string $fileName, string $message = '') : voi
160207
$code = \file_get_contents($fileName);
161208

162209
$this->assertValidPHP($code, $message);
210+
211+
if (! $this->skipNamespaceTest)
212+
{
213+
$namespace = $this->classFinder->getNamespace();
214+
if (! \in_array($namespace, $this->skipNamespaces))
215+
{
216+
// assert namespace is correct
217+
$fileName = \str_replace('/', '\\', $fileName);
218+
$this->assertStringContainsString($namespace . '\\', $fileName, "Namespace {$namespace} not found in file path {$fileName}");
219+
}
220+
}
163221
}
164222
}

tests/UnitTest.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
*/
1212
class UnitTest extends \PHPFUI\PHPUnitSyntaxCoverage\Extensions
1313
{
14-
1514
public function testDirectory() : void
1615
{
1716
$this->assertValidPHPDirectory(__DIR__, 'Test directory is not valid', false);
@@ -46,7 +45,12 @@ public function testValidPHPFile() : void
4645

4746
public function testVendorDirectory() : void
4847
{
48+
$this->skipNamespaceTesting();
49+
// Sloppy coding from various packages causes us to have to skip directories. If only they used PHPUnitSyntaxCoverage they would have detected these issues!
50+
$this->addSkipDirectory('package-versions-deprecated'); // phpunit
51+
$this->addSkipDirectory('php-cs-fixer');
52+
$this->addSkipDirectory('DependencyInjection'); // Symfony\Component\DependencyInjection
53+
$this->addSkipDirectory('path-util'); // Webmozart\PathUtil
4954
$this->assertValidPHPDirectory(__DIR__ . '/../vendor', 'Vendor directory is not valid');
5055
}
51-
5256
}

tests/bootstrap.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
<?php
2-
error_reporting(E_ALL);
2+
3+
\error_reporting(E_ALL);
4+
5+
\ini_set('memory_limit', '-1');
36

47
function classNameExists(string $className) : string
58
{
69
$path = __DIR__ . "/{$className}.php";
7-
$path = str_replace('\\', '/', $path);
8-
return file_exists($path) ? $path : '';
10+
$path = \str_replace('\\', '/', $path);
11+
12+
return \file_exists($path) ? $path : '';
913
}
1014

1115
function autoload($className) : void
1216
{
1317
$path = classNameExists($className);
1418

1519
if ($path)
16-
{
17-
/** @noinspection PhpIncludeInspection */
18-
include $path;
19-
}
20+
{
21+
/** @noinspection PhpIncludeInspection */
22+
include $path;
23+
}
2024
}
21-
spl_autoload_register('autoload');
25+
\spl_autoload_register('autoload');
2226

2327
require_once 'vendor/autoload.php';

0 commit comments

Comments
 (0)