Skip to content

Commit

Permalink
Allow nonexistent paths in excludePaths by appending (?)
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 3, 2024
1 parent e2c9b4a commit 39649c2
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 35 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ jobs:
echo "$OUTPUT"
../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT"
../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT"
- script: |
cd e2e/bad-exclude-paths
OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon)
echo "$OUTPUT"
- script: |
cd e2e/bad-exclude-paths
cp -r tmp-node-modules node_modules
OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon)
echo "$OUTPUT"
steps:
- name: "Checkout"
Expand Down
10 changes: 10 additions & 0 deletions e2e/bad-exclude-paths/ignoreNonexistentExcludePath.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
includes:
- ../../conf/bleedingEdge.neon

parameters:
level: 8
paths:
- .
excludePaths:
- node_modules (?)
- tmp-node-modules
3 changes: 3 additions & 0 deletions e2e/bad-exclude-paths/tmp-node-modules/test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

echo [];
12 changes: 12 additions & 0 deletions src/DependencyInjection/Neon/OptionalPath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types = 1);

namespace PHPStan\DependencyInjection\Neon;

final class OptionalPath
{

public function __construct(public readonly string $path)
{
}

}
38 changes: 28 additions & 10 deletions src/DependencyInjection/NeonAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
use Nette\Neon\Entity;
use Nette\Neon\Exception;
use Nette\Neon\Neon;
use PHPStan\DependencyInjection\Neon\OptionalPath;
use PHPStan\File\FileHelper;
use PHPStan\File\FileReader;
use function array_values;
use function array_walk_recursive;
use function count;
use function dirname;
use function implode;
use function in_array;
Expand All @@ -29,7 +31,7 @@
class NeonAdapter implements Adapter
{

public const CACHE_KEY = 'v26-no-implicit-wildcard';
public const CACHE_KEY = 'v27-optional-path';

private const PREVENT_MERGING_SUFFIX = '!';

Expand Down Expand Up @@ -65,6 +67,13 @@ public function process(array $arr, string $fileKey, string $file): array
$val[Helpers::PREVENT_MERGING] = true;
}

$keyToResolve = $fileKey;
if (is_int($key)) {
$keyToResolve .= '[]';
} else {
$keyToResolve .= '[' . $key . ']';
}

if (is_array($val)) {
if (!is_int($key)) {
$fileKeyToPass = $fileKey . '[' . $key . ']';
Expand All @@ -89,18 +98,27 @@ public function process(array $arr, string $fileKey, string $file): array
}
$val = $tmp;
} else {
$tmp = $this->process([$val->value], $fileKeyToPass, $file);
$val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file));
if (
in_array($keyToResolve, [
'[parameters][excludePaths][]',
'[parameters][excludePaths][analyse][]',
'[parameters][excludePaths][analyseAndScan][]',
], true)
&& count($val->attributes) === 1
&& $val->attributes[0] === '?'
&& is_string($val->value)
&& !str_contains($val->value, '%')
&& !str_starts_with($val->value, '*')
) {
$fileHelper = $this->createFileHelperByFile($file);
$val = new OptionalPath($fileHelper->normalizePath($fileHelper->absolutizePath($val->value)));
} else {
$tmp = $this->process([$val->value], $fileKeyToPass, $file);
$val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file));
}
}
}

$keyToResolve = $fileKey;
if (is_int($key)) {
$keyToResolve .= '[]';
} else {
$keyToResolve .= '[' . $key . ']';
}

if (in_array($keyToResolve, [
'[parameters][paths][]',
'[parameters][excludes_analyse][]',
Expand Down
64 changes: 39 additions & 25 deletions src/DependencyInjection/ValidateExcludePathsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
namespace PHPStan\DependencyInjection;

use Nette\DI\CompilerExtension;
use PHPStan\DependencyInjection\Neon\OptionalPath;
use PHPStan\File\FileExcluder;
use function array_key_exists;
use function array_map;
use function array_merge;
use function array_unique;
use function count;
use function is_dir;
use function is_file;
Expand All @@ -21,45 +22,58 @@ class ValidateExcludePathsExtension extends CompilerExtension
public function loadConfiguration(): void
{
$builder = $this->getContainerBuilder();
if (!$builder->parameters['__validate']) {
return;
}

$excludePaths = $builder->parameters['excludePaths'];
if ($excludePaths === null) {
return;
}

$errors = [];
$noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard'];
if (!$noImplicitWildcard) {
return;
if ($builder->parameters['__validate'] && $noImplicitWildcard) {
$paths = [];
if (array_key_exists('analyse', $excludePaths)) {
$paths = $excludePaths['analyse'];
}
if (array_key_exists('analyseAndScan', $excludePaths)) {
$paths = array_merge($paths, $excludePaths['analyseAndScan']);
}
foreach ($paths as $path) {
if ($path instanceof OptionalPath) {
continue;
}
if (FileExcluder::isAbsolutePath($path)) {
if (is_dir($path)) {
continue;
}
if (is_file($path)) {
continue;
}
}
if (FileExcluder::isFnmatchPattern($path)) {
continue;
}

$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path);
}
}

$paths = [];
$newExcludePaths = [];
if (array_key_exists('analyse', $excludePaths)) {
$paths = $excludePaths['analyse'];
$newExcludePaths['analyse'] = $excludePaths['analyse'];
}
if (array_key_exists('analyseAndScan', $excludePaths)) {
$paths = array_merge($paths, $excludePaths['analyseAndScan']);
$newExcludePaths['analyseAndScan'] = $excludePaths['analyseAndScan'];
}

$errors = [];
foreach (array_unique($paths) as $path) {
if (FileExcluder::isAbsolutePath($path)) {
if (is_dir($path)) {
continue;
}
if (is_file($path)) {
continue;
}
}
if (FileExcluder::isFnmatchPattern($path)) {
continue;
}

$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path);
foreach ($newExcludePaths as $key => $p) {
$newExcludePaths[$key] = array_map(
static fn ($path) => $path instanceof OptionalPath ? $path->path : $path,
$p,
);
}

$builder->parameters['excludePaths'] = $newExcludePaths;

if (count($errors) === 0) {
return;
}
Expand Down

0 comments on commit 39649c2

Please sign in to comment.