-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a Phan plugin for use with MediaWiki extensions or skins that checks that classes that can have dependencies injected do indeed have those dependencies injected. SEL-758
- Loading branch information
0 parents
commit 5dc4d1a
Showing
22 changed files
with
442 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* text eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
name: Continuous Integration | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
jobs: | ||
style-php: | ||
name: Code Style (PHP) | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: wikiteq/php-lint-action@main | ||
|
||
test: | ||
name: PHPUnit | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Setup PHP | ||
uses: shivammathur/setup-php@v2 | ||
with: | ||
php-version: '8.1' | ||
extensions: mbstring, intl | ||
coverage: none | ||
tools: composer | ||
|
||
- uses: actions/checkout@v4 | ||
|
||
- name: Setup Composer | ||
run: composer update | ||
shell: bash | ||
|
||
- name: Run PHPUnit | ||
uses: php-actions/phpunit@v4 | ||
with: | ||
configuration: phpunit.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
vendor | ||
composer.lock | ||
.phpunit.result.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?xml version="1.0"?> | ||
<ruleset> | ||
<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki" /> | ||
<file>.</file> | ||
<arg name="extensions" value="php"/> | ||
<arg name="encoding" value="UTF-8"/> | ||
<exclude-pattern type="relative">^tests/cases/*</exclude-pattern> | ||
<exclude-pattern type="relative">^tests/stubs/*</exclude-pattern> | ||
</ruleset> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
WikiTeq Proprietary License | ||
|
||
Copyright (c) 2024 WikiTeq | ||
|
||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are not permitted without prior written consent from WikiTeq. | ||
|
||
Neither the name of WikiTeq nor the names of its contributors may be used to | ||
endorse or promote products derived from this software without specific prior | ||
written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY WIKITEQ "AS IS" AND ANY EXPRESS OR IMPLIED | ||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||
EVENT SHALL WIKITEQ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | ||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | ||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php | ||
|
||
declare( strict_types=1 ); | ||
|
||
namespace WikiTeq\MediaWikiServicesCheckPlugin; | ||
|
||
use ast\Node; | ||
use Phan\AST\UnionTypeVisitor; | ||
use Phan\Language\Type; | ||
use Phan\Language\UnionType; | ||
use Phan\PluginV3; | ||
use Phan\PluginV3\PluginAwarePostAnalysisVisitor; | ||
use Phan\PluginV3\PostAnalyzeNodeCapability; | ||
|
||
/** | ||
* Plugin to add our MediaWikiServicesVisitor that gets used for every AST node | ||
*/ | ||
class MediaWikiServicesCheckPlugin extends PluginV3 implements | ||
PostAnalyzeNodeCapability | ||
{ | ||
|
||
public static function getPostAnalyzeNodeVisitorClassName(): string { | ||
return MediaWikiServicesVisitor::class; | ||
} | ||
} | ||
|
||
/** | ||
* Visitor that detects MediaWikiServices::getInstance()->getService() and | ||
* similar in places where services can (and should) be injected. | ||
*/ | ||
// phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound | ||
class MediaWikiServicesVisitor extends PluginAwarePostAnalysisVisitor { | ||
|
||
public function visitMethodCall( Node $node ): void { | ||
$typeUnion = UnionTypeVisitor::unionTypeFromNode( | ||
$this->code_base, | ||
$this->context, | ||
$node->children['expr'] | ||
); | ||
|
||
// Use UnionType to simplify comparison with the type of expr | ||
$mwServicesType = UnionType::fromFullyQualifiedPHPDocString( | ||
'\\MediaWiki\\MediaWikiServices' | ||
); | ||
$isMWServices = $mwServicesType->isEqualTo( $typeUnion ); | ||
if ( !$isMWServices ) { | ||
return; | ||
} | ||
|
||
// Check that we are in a non-static method in a class that should | ||
// support dependency injection | ||
if ( !$this->context->isInClassScope() | ||
|| !$this->context->isInFunctionLikeScope() | ||
) { | ||
return; | ||
} | ||
|
||
$funcLike = $this->context->getFunctionLikeInScope( $this->code_base ); | ||
if ( $funcLike->isStatic() ) { | ||
return; | ||
} | ||
|
||
$method = $node->children['method']; | ||
$methodStart = substr( $method, 0, 3 ); | ||
if ( $methodStart !== 'get' && $methodStart !== 'has' ) { | ||
// Something more complicated like peeking, ignore | ||
return; | ||
} | ||
|
||
// Map of class to detect extending => placeholder for message | ||
// The *FIRST* matching message is used, to allow for more specific | ||
// messages | ||
$baseClassMap = [ | ||
'\\SpecialPage' => 'Special pages', | ||
'\\ApiQueryBase' => 'API query modules', | ||
'\\ApiBase' => 'API modules', | ||
]; | ||
|
||
$scope = $this->context->getScope(); | ||
$currentClass = $scope->getClassFQSEN()->asType(); | ||
foreach ( $baseClassMap as $baseClass => $msg ) { | ||
$baseClassType = Type::fromFullyQualifiedString( $baseClass ); | ||
if ( !$currentClass->isSubclassOf( | ||
$baseClassType, | ||
$this->code_base | ||
) ) { | ||
continue; | ||
} | ||
$this->emit( | ||
'MediaWikiServicesAccessed', | ||
'%s should have services injected with dependency injection', | ||
[ $msg ] | ||
); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
// Plugins return an instance of themselves at the end of their definition files | ||
return new MediaWikiServicesCheckPlugin(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "wikiteq/phan-mediawikiservices-check-plugin", | ||
"description": "Phan plugin for checking uses of MediaWiki's MediaWikiServices class", | ||
"authors": [ | ||
{ | ||
"name": "Daniel Scherzer", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"phan/phan": "5.4.3", | ||
"php": ">=7.4.0" | ||
}, | ||
"require-dev": { | ||
"mediawiki/mediawiki-codesniffer": "43.0.0", | ||
"php-parallel-lint/php-console-highlighter": "1.0.0", | ||
"php-parallel-lint/php-parallel-lint": "1.4.0", | ||
"phpunit/phpunit": "9.6.16" | ||
}, | ||
"scripts": { | ||
"test": [ | ||
"composer phpcs", | ||
"phpunit" | ||
], | ||
"phpcs": "phpcs -p -s", | ||
"fix": [ | ||
"phpcbf" | ||
] | ||
}, | ||
"config": { | ||
"allow-plugins": { | ||
"dealerdirect/phpcodesniffer-composer-installer": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0"?> | ||
<phpunit | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
convertErrorsToExceptions="true" | ||
convertNoticesToExceptions="true" | ||
convertWarningsToExceptions="true" | ||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" | ||
> | ||
<coverage/> | ||
<testsuites> | ||
<testsuite name="MediaWikiServicesCheckPlugin tests"> | ||
<directory>./tests</directory> | ||
</testsuite> | ||
</testsuites> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
namespace WikiTeq\MediaWikiServicesCheckPlugin\Tests; | ||
|
||
use DirectoryIterator; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* @covers \WikiTeq\MediaWikiServicesCheckPlugin\MediaWikiServicesCheckPlugin | ||
*/ | ||
class PluginTest extends TestCase { | ||
|
||
/** @dataProvider provideTestCases */ | ||
public function testScenarios( $testCaseDir, $expectedIssues ) { | ||
$testDirPath = realpath( './tests' ); | ||
// $this->assertSame( $testDirPath, '' ); | ||
// Go back to the main directory | ||
chdir( __DIR__ . '/../' ); | ||
// Build the command to run | ||
$cmd = "php vendor/phan/phan/phan" . | ||
" --allow-polyfill-parser" . | ||
" -d \"$testDirPath\"" . | ||
" -k \"test-config.php\"" . | ||
" -l \"stubs\"" . | ||
" -l \"cases/$testCaseDir\""; | ||
// phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.shell_exec | ||
$phanOutput = shell_exec( $cmd ) ?? ''; | ||
// Trim to avoid issues with newlines | ||
$this->assertSame( trim( $expectedIssues ), trim( $phanOutput ) ); | ||
} | ||
|
||
public function provideTestCases() { | ||
$dirIter = new DirectoryIterator( __DIR__ . '/cases' ); | ||
foreach ( $dirIter as $directory ) { | ||
if ( !$directory->isDot() ) { | ||
$folder = $directory->getPathname(); | ||
// In `/cases` we have a bunch of sub directories, each with | ||
// PHP files to analyze and then `expected.txt` with the results | ||
$testName = basename( $folder ); | ||
$expected = file_get_contents( $folder . '/expected.txt' ); | ||
yield $testName => [ $testName, $expected ]; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
class NormalApi extends ApiBase { | ||
|
||
public function test1( $par ) { | ||
// getService() with arbitrary service name | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
class QueryApi extends ApiQueryBase { | ||
|
||
public function test1( $par ) { | ||
// getService() with arbitrary service name | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
cases/Api/NormalApi.php:9 MediaWikiServicesAccessed API modules should have services injected with dependency injection | ||
cases/Api/QueryApi.php:9 MediaWikiServicesAccessed API query modules should have services injected with dependency injection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
class MyFormSpecial extends FormSpecialPage { | ||
|
||
public function test1( $par ) { | ||
// getService() with arbitrary service name | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
class MySpecial extends SpecialPage { | ||
|
||
public function test1( $par ) { | ||
// getService() with arbitrary service name | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
public function test2( $par ) { | ||
// get() with arbitrary service name | ||
$service = MediaWikiServices::getInstance()->get( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
public function test3( $par ) { | ||
// service-specific getter | ||
$service = MediaWikiServices::getInstance()->getUserFactory(); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
cases/SpecialPage/FormSpecial.php:9 MediaWikiServicesAccessed Special pages should have services injected with dependency injection | ||
cases/SpecialPage/MySpecial.php:9 MediaWikiServicesAccessed Special pages should have services injected with dependency injection | ||
cases/SpecialPage/MySpecial.php:15 MediaWikiServicesAccessed Special pages should have services injected with dependency injection | ||
cases/SpecialPage/MySpecial.php:21 MediaWikiServicesAccessed Special pages should have services injected with dependency injection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
namespace FooBar; | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
// Not the real SpecialPage class since we are in a namespace | ||
// @phan-suppress-next-line PhanUndeclaredExtendedClass | ||
class MySpecial extends SpecialPage { | ||
|
||
public function test1() { | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
use MediaWiki\MediaWikiServices; | ||
|
||
class MySpecial extends SpecialPage { | ||
|
||
public function testPeek() { | ||
return MediaWikiServices::getInstance()->peekService( 'MyService' ); | ||
} | ||
|
||
public static function testStatic( $par ) { | ||
// Static method should be ignored | ||
$service = MediaWikiServices::getInstance()->getService( 'MyService' ); | ||
$service->run( $par ); | ||
} | ||
|
||
} |
Empty file.
Oops, something went wrong.