Skip to content

Commit

Permalink
Add support for absolute plugin paths.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwage authored and muglug committed May 5, 2019
1 parent dd40987 commit ecb7a6c
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 14 deletions.
9 changes: 8 additions & 1 deletion docs/authoring_plugins.md
Expand Up @@ -24,12 +24,19 @@ Here are a couple of example plugins:
- [FunctionCasingChecker](https://github.com/vimeo/psalm/blob/master/examples/plugins/FunctionCasingChecker.php) - checks that your functions and methods are correctly-cased

To ensure your plugin runs when Psalm does, add it to your [config](configuration.md):
```php
```xml
<plugins>
<plugin filename="src/plugins/SomePlugin.php" />
</plugins>
```

You can also specify an absolute path to your plugin:
```xml
<plugins>
<plugin filename="/path/to/SomePlugin.php" />
</plugins>
```

## Type system

Understand how Psalm handles types by [reading this guide](plugins_type_system.md).
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd"
bootstrap="vendor/autoload.php"
bootstrap="tests/bootstrap.php"
backupGlobals="false"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
Expand Down
6 changes: 4 additions & 2 deletions src/Psalm/Config.php
Expand Up @@ -792,9 +792,11 @@ public static function loadFromXML($base_dir, $file_contents)
if (isset($config_xml->plugins->plugin)) {
/** @var \SimpleXMLElement $plugin */
foreach ($config_xml->plugins->plugin as $plugin) {
$plugin_file_name = $plugin['filename'];
$plugin_file_name = (string) $plugin['filename'];

$path = $config->base_dir . $plugin_file_name;
$path = isAbsolutePath($plugin_file_name)
? $plugin_file_name
: $config->base_dir . $plugin_file_name;

$config->addPluginPath($path);
}
Expand Down
29 changes: 29 additions & 0 deletions src/command_functions.php
Expand Up @@ -220,3 +220,32 @@ function getPathsToCheck($f_paths)

return $paths_to_check;
}

/**
* @param string $path
*
* @return bool
*/
function isAbsolutePath($path)
{
// Optional wrapper(s).
$regex = '%^(?<wrappers>(?:[[:print:]]{2,}://)*)';

// Optional root prefix.
$regex .= '(?<root>(?:[[:alpha:]]:/|/)?)';

// Actual path.
$regex .= '(?<path>(?:[[:print:]]*))$%';

$parts = [];

if (!preg_match($regex, $path, $parts)) {
throw new InvalidArgumentException(sprintf('Path is not valid, "%s" given.', $path));
}

if ('' !== $parts['root']) {
return true;
}

return false;
}
36 changes: 36 additions & 0 deletions tests/IsAbsolutePathTest.php
@@ -0,0 +1,36 @@
<?php
namespace Psalm\Tests;

class IsAbsolutePathTest extends TestCase
{
/**
* @param string $path
* @param bool $expected
*
* @return void
*
* @dataProvider providerForTestIsAbsolutePath
*/
public function testIsAbsolutePath($path, $expected)
{
require_once __DIR__.'/../src/command_functions.php';

self::assertSame($expected, isAbsolutePath($path));
}

/**
* @return array<int, array{0:string, 1:bool}>
*/
public function providerForTestIsAbsolutePath()
{
return [
['/path/to/something', true],
['/path/to/something/file.php', true],
['relative/path/to/something', false],
['relative/path/to/something/file.php', false],
['c:/path/to/something', true],
['file://c:/path/to/something', true],
['zlib://c:/path/to/something', true],
];
}
}
55 changes: 55 additions & 0 deletions tests/PluginTest.php
Expand Up @@ -770,4 +770,59 @@ public function testAfterAnalysisHooks()
$this->project_analyzer->check('tests/DummyProject', true);
\Psalm\IssueBuffer::finish($this->project_analyzer, true, microtime(true));
}

/**
* @return void
*/
public function testPluginFilenameCanBeAbsolute()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__) . DIRECTORY_SEPARATOR,
sprintf(
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
<plugins>
<plugin filename="%s/examples/plugins/StringChecker.php" />
</plugins>
</psalm>',
__DIR__.'/..'
)
)
);

$this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer);
}

/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage does-not-exist/plugins/StringChecker.php
*
* @return void
*/
public function testPluginInvalidAbsoluteFilenameThrowsException()
{
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__) . DIRECTORY_SEPARATOR,
sprintf(
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
<plugins>
<plugin filename="%s/does-not-exist/plugins/StringChecker.php" />
</plugins>
</psalm>',
__DIR__.'/..'
)
)
);

$this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer);
}
}
12 changes: 2 additions & 10 deletions tests/bootstrap.php
@@ -1,13 +1,5 @@
<?php

ini_set('display_startup_errors', '1');
ini_set('html_errors', '1');
ini_set('memory_limit', '-1');
error_reporting(E_ALL);
require_once(__DIR__.'/../src/command_functions.php');

foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
if (file_exists($file)) {
require $file;
break;
}
}
return require_once(__DIR__.'/../vendor/autoload.php');

0 comments on commit ecb7a6c

Please sign in to comment.