Skip to content
Permalink
Browse files

Add autodiscovery of --init folders based on composer.json

  • Loading branch information...
muglug committed May 9, 2019
1 parent fe22e83 commit 335b04186bf81819921171dd115e0593d287cf1f
@@ -9,11 +9,11 @@ Make sure to check out the [Contributing to Open Source on GitHub](https://guide
You can create an issue [here](https://github.com/vimeo/psalm/issues/new), but before you do, follow these guidelines:

* Make sure that you are using the latest version (`dev-master`).
* It’s by no means a requirement, but if it's a bug, and you provide demonstration code that can be pasted into https://getpsalm.org, it will likely get fixed faster.
* It’s by no means a requirement, but if it's a bug, and you provide demonstration code that can be pasted into https://psalm.dev, it will likely get fixed faster.

## Pull Requests

Before you send a pull request, make sure you follow these guidelines:

* Make sure to run `composer tests` and `./psalm --find-dead-code` to ensure that Travis builds will pass
* Make sure to run `composer tests` and `./psalm --find-dead-code` to ensure that Travis builds will pass
* Don’t forget to add tests!
@@ -12,7 +12,7 @@

<!-- These are just examples and stub classes/files, so it doesn't really matter if they're PSR-2 compliant. -->
<exclude-pattern>src/Psalm/Internal/Stubs/</exclude-pattern>
<exclude-pattern>tests/fixtures/stubs/</exclude-pattern>
<exclude-pattern>tests/fixtures/</exclude-pattern>
<rule ref="Generic.Files.LineLength">
<exclude-pattern>tests</exclude-pattern>
</rule>
@@ -0,0 +1,125 @@
<?php
namespace Psalm\Config;
use Psalm\Exception\ConfigCreationException;
use Psalm\Internal\Provider;
class Creator
{
public static function getContents(
string $current_dir,
string $suggested_dir = null,
int $level = 3
) : string {
$replacements = [];
if ($suggested_dir) {
if (is_dir($current_dir . DIRECTORY_SEPARATOR . $suggested_dir)) {
$replacements[] = '<directory name="' . $suggested_dir . '" />';
} else {
$bad_dir_path = $current_dir . DIRECTORY_SEPARATOR . $suggested_dir;
throw new ConfigCreationException(
'The given path "' . $bad_dir_path . '" does not appear to be a directory'
);
}
} elseif (is_dir($current_dir . DIRECTORY_SEPARATOR . 'src')) {
$replacements[] = '<directory name="src" />';
} else {
$composer_json_location = $current_dir . DIRECTORY_SEPARATOR . 'composer.json';
if (!file_exists($composer_json_location)) {
throw new ConfigCreationException(
'Problem during config autodiscovery - could not find composer.json during initialization.'
);
}
/** @psalm-suppress MixedAssignment */
if (!$composer_json = json_decode(file_get_contents($composer_json_location), true)) {
throw new ConfigCreationException('Invalid composer.json at ' . $composer_json_location);
}
if (!is_array($composer_json)) {
throw new ConfigCreationException('Invalid composer.json at ' . $composer_json_location);
}
$replacements = self::getPsr4Paths($current_dir, $composer_json);
if (!$replacements) {
throw new ConfigCreationException(
'Could not located any PSR-4-compatible paths in ' . $composer_json_location
);
}
}
$template_file_name = dirname(__DIR__, 3) . '/assets/config_levels/' . $level . '.xml';
if (!file_exists($template_file_name)) {
throw new ConfigCreationException('Could not open config template ' . $template_file_name);
}
$template = (string)file_get_contents($template_file_name);
$template = str_replace(
'<directory name="src" />',
implode("\n ", $replacements),
$template
);
return $template;
}
/**
* @return string[]
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedOperand
*/
private static function getPsr4Paths(string $current_dir, array $composer_json) : array
{
if (!isset($composer_json['autoload']['psr-4'])) {
return [];
}
$nodes = [];
/** @var string|string[] $path */
foreach ($composer_json['autoload']['psr-4'] as $paths) {
if (!is_array($paths)) {
$paths = [$paths];
}
foreach ($paths as $path) {
if ($path === '') {
/** @var string[] */
$php_files = array_merge(
glob($current_dir . DIRECTORY_SEPARATOR . '*.php'),
glob($current_dir . DIRECTORY_SEPARATOR . '**/*.php'),
glob($current_dir . DIRECTORY_SEPARATOR . '**/**/*.php')
);
foreach ($php_files as $php_file) {
$parts = explode(DIRECTORY_SEPARATOR, $php_file);
if ($parts[0] === 'vendor') {
continue;
}
if (count($parts) === 1) {
$nodes[] = '<file name="' . $php_file . '" />';
} else {
$nodes[] = '<file name="' . $parts[0] . '" />';
}
}
} else {
$nodes[] = '<directory name="' . $path . '" />';
}
}
}
$nodes = array_unique($nodes);
sort($nodes);
return $nodes;
}
}
@@ -2,6 +2,7 @@
namespace Psalm\Config;
use SimpleXMLElement;
use Psalm\Exception\ConfigException;
class FileFilter
{
@@ -116,9 +117,10 @@ public static function loadFromXMLElement(
continue;
}
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$directory['name'] . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir
. DIRECTORY_SEPARATOR . (string)$directory['name']
);
}
foreach ($globs as $glob_index => $directory_path) {
@@ -127,9 +129,10 @@ public static function loadFromXMLElement(
continue;
}
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$directory['name'] . ':' . $glob_index . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir
. DIRECTORY_SEPARATOR . (string)$directory['name'] . ':' . $glob_index
);
}
if (!$directory_path) {
@@ -156,15 +159,17 @@ public static function loadFromXMLElement(
continue;
}
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$directory['name'] . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir
. DIRECTORY_SEPARATOR . (string)$directory['name']
);
}
if (!is_dir($directory_path)) {
echo $base_dir . DIRECTORY_SEPARATOR . (string)$directory['name']
. ' is not a directory ' . PHP_EOL;
exit(1);
throw new ConfigException(
$base_dir . DIRECTORY_SEPARATOR . (string)$directory['name']
. ' is not a directory'
);
}
/** @var \RecursiveDirectoryIterator */
@@ -226,16 +231,18 @@ public static function loadFromXMLElement(
);
if (empty($globs)) {
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name'] . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name']
);
}
foreach ($globs as $glob_index => $file_path) {
if (!$file_path) {
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name'] . ':' . $glob_index . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name'] . ':' . $glob_index
);
}
$filter->addFile($file_path);
}
@@ -245,9 +252,10 @@ public static function loadFromXMLElement(
$file_path = realpath($prospective_file_path);
if (!$file_path) {
echo 'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name'] . PHP_EOL;
exit(1);
throw new ConfigException(
'Could not resolve config path to ' . $base_dir . DIRECTORY_SEPARATOR .
(string)$file['name']
);
}
$filter->addFile($file_path);
@@ -274,8 +282,9 @@ public static function loadFromXMLElement(
$method_id = (string)$referenced_method['name'];
if (!preg_match('/^[^:]+::[^:]+$/', $method_id) && !static::isRegularExpression($method_id)) {
echo 'Invalid referencedMethod ' . $method_id . PHP_EOL;
exit(1);
throw new ConfigException(
'Invalid referencedMethod ' . $method_id
);
}
$filter->method_ids[] = strtolower($method_id);
@@ -0,0 +1,6 @@
<?php
namespace Psalm\Exception;
class ConfigCreationException extends \Exception
{
}
@@ -345,7 +345,7 @@ function ($arg) {
));
$level = 3;
$source_dir = 'src';
$source_dir = null;
if (count($args)) {
if (count($args) > 2) {
@@ -363,31 +363,13 @@ function ($arg) {
$source_dir = $args[0];
}
if (!is_dir($source_dir)) {
$bad_dir_path = getcwd() . DIRECTORY_SEPARATOR . $source_dir;
if (!isset($args[0])) {
die('Please specify a directory - the default, "src", was not found in this project.' . PHP_EOL);
}
die('The given path "' . $bad_dir_path . '" does not appear to be a directory' . PHP_EOL);
}
$template_file_name = dirname(__DIR__) . '/assets/config_levels/' . $level . '.xml';
if (!file_exists($template_file_name)) {
die('Could not open config template ' . $template_file_name . PHP_EOL);
try {
$template_contents = Psalm\Config\Creator::getContents($current_dir, $source_dir, $level);
} catch (Psalm\Exception\ConfigCreationException $e) {
die($e->getMessage() . PHP_EOL);
}
$template = (string)file_get_contents($template_file_name);
$template = str_replace(
'<directory name="src" />',
'<directory name="' . $source_dir . '" />',
$template
);
if (!file_put_contents($current_dir . 'psalm.xml', $template)) {
if (!file_put_contents($current_dir . 'psalm.xml', $template_contents)) {
die('Could not write to psalm.xml' . PHP_EOL);
}
@@ -450,7 +432,7 @@ function ($arg) {
$config = Config::getConfigForPath($current_dir, $current_dir, $output_format);
}
} catch (Psalm\Exception\ConfigException $e) {
echo $e->getMessage();
echo $e->getMessage() . PHP_EOL;
exit(1);
}
@@ -0,0 +1,56 @@
<?php
namespace Psalm\Tests\Config;
use Psalm\Config\Creator;
class CreatorTest extends \Psalm\Tests\TestCase
{
/**
* @return void
*/
public static function setUpBeforeClass()
{
}
/**
* @return void
*/
public function setUp()
{
}
/**
* @return void
*/
public function testDiscoverLibDirectory()
{
$lib_contents = Creator::getContents(
dirname(__DIR__, 1)
. DIRECTORY_SEPARATOR . 'fixtures'
. DIRECTORY_SEPARATOR . 'config_discovery'
. DIRECTORY_SEPARATOR . 'files_in_lib',
null,
1
);
$this->assertSame('<?xml version="1.0"?>
<psalm
totallyTyped="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
</issueHandlers>
</psalm>
', $lib_contents);
}
}
@@ -0,0 +1,8 @@
{
"name": "dummy/lib",
"autoload": {
"psr-4": {
"Foo\\Bar": "lib"
}
}
}
@@ -0,0 +1,5 @@
<?php
namespace Foo\Bar;
class Baz {}
@@ -0,0 +1,3 @@
<?php
// some file

0 comments on commit 335b041

Please sign in to comment.
You can’t perform that action at this time.