|
| 1 | +SourceMapper Implementation Notes |
| 2 | +================================= |
| 3 | + |
| 4 | +Currently the `<directory>` element in `<include>` and `<exclude>` has the |
| 5 | +attributes `prefix` and `suffix` and we also have `<file>` which specifies a |
| 6 | +single file. |
| 7 | + |
| 8 | +Our primary goal is to ensure that the source mapper needn't iterate over all |
| 9 | +included files recursively whenever the source map is required (for example |
| 10 | +when a deprecation is encountered PHPUnit needs to know if the deprecation was |
| 11 | +issued from source code within the project's responsiblity - i.e. source that |
| 12 | +is mapped). We can determine if a file is within the included source by |
| 13 | +converting the glob-patterns in the `<directory>` element to regexes. |
| 14 | + |
| 15 | +This is more complicated than it could be as the current matching logic |
| 16 | +depends on PHP's `glob` function - the implementation of which is not |
| 17 | +consistent across platforms and which has a number of rarely-used operators |
| 18 | +which while not common, would present a B/C break if they were removed. |
| 19 | + |
| 20 | +How it works currently |
| 21 | +---------------------- |
| 22 | + |
| 23 | +```php |
| 24 | +# src/TextUI/Configuration/SourceMapper.php |
| 25 | +foreach ($includeDirectories as $path => [$prefixes, $suffixes]) { |
| 26 | + foreach ((new FileIteratorFacade)->getFilesAsArray($path, $suffixes, $prefixes) as $file) { |
| 27 | +``` |
| 28 | + |
| 29 | +```php |
| 30 | +# vendor/phpunit/php-file-iterator/src/Facade.php |
| 31 | +public function getFilesAsArray(array|string $paths, array|string $suffixes = '', array|string $prefixes = '', array $exclude = []): array |
| 32 | +{ |
| 33 | + $iterator = (new Factory)->getFileIterator($paths, $suffixes, $prefixes, $exclude); |
| 34 | +``` |
| 35 | + |
| 36 | +```php |
| 37 | +# vendor/phpunit/php-file-iterator/src/Factory.php |
| 38 | +``` |
| 39 | + |
| 40 | +The Factory: |
| 41 | + |
| 42 | +- resolves (expands) any wildcards to concrete **file** paths |
| 43 | +- iterates over the paths, ignoring any directories (by checking `is_dir`)[1] |
| 44 | +- create a new PHPUnit `Iterator` passing a recursive iterator iterator |
| 45 | + directory iterator... (the iterator that iterates over the directories) that |
| 46 | + **follows symlinks and skips dots**. |
| 47 | + |
| 48 | +The Iterator: |
| 49 | + |
| 50 | +- Filters out paths if `realpath` returns `false`. |
| 51 | +- Filters out hidden paths at the root of the resovled path. |
| 52 | +- Filters out concretee paths based on the `basename` and any given suffix or |
| 53 | + prefix. |
| 54 | + |
| 55 | +Features |
| 56 | +-------- |
| 57 | + |
| 58 | +- Symlinks: |
| 59 | +- Skips hidden files |
| 60 | + |
| 61 | +Assumptions (to verify) |
| 62 | +----------------------- |
| 63 | + |
| 64 | +The meaning of `<directory prefix="Test" suffix=".phpt">tests/</directory>` |
| 65 | +means "all files in the `tests` directory whose (base)names begin with `Test` and |
| 66 | +end with `.phpt`, for example `tests/Foobar/Barfoo/TestBar.phpt` would be a |
| 67 | +match. |
| 68 | + |
| 69 | +While `<file>tests/Bar/Foo.php</file>` would be a literal reference to a |
| 70 | +single file. |
| 71 | + |
| 72 | +Option 1: Simplify |
| 73 | +------------------ |
| 74 | + |
| 75 | +Instead of having `<file>` and `<directory>` elements we can simplify them to |
| 76 | +a single element that has no attributes: |
| 77 | + |
| 78 | +```xml |
| 79 | +<include> |
| 80 | + <pattern>tests/**/Test*.php</pattern> |
| 81 | + <pattern>tests/Bar/Foo.php</pattern> |
| 82 | +</include> |
| 83 | +``` |
| 84 | + |
| 85 | +Would be equivalent to: |
| 86 | + |
| 87 | +```xml |
| 88 | +<include> |
| 89 | + <directory prefix="Test" suffix=".php">tests/</directory> |
| 90 | + <file>tests/Bar/Foo.php</file> |
| 91 | +</include> |
| 92 | +``` |
| 93 | + |
| 94 | +Option 2: Preserve |
| 95 | +------------------ |
| 96 | + |
| 97 | +In this case we preserve the existing API (but accept that the glob |
| 98 | +implementation has changed, though it will work in 99% of cases the behavior |
| 99 | +will change vs. the current implementation). |
| 100 | + |
| 101 | +```xml |
| 102 | +<directory prefix="Test" suffix=".php">tests/</directory> |
| 103 | +``` |
| 104 | + |
| 105 | +Would be: `tests/**/Test*.php` |
| 106 | + |
| 107 | +```xml |
| 108 | +<directory>tests/</directory> |
| 109 | +``` |
| 110 | + |
| 111 | +Would be: `tests/**/*` |
| 112 | + |
| 113 | +```xml |
| 114 | +<directory>tests</directory> |
| 115 | +``` |
| 116 | + |
| 117 | +Would be: `tests/**/*` |
| 118 | + |
| 119 | +```xml |
| 120 | +<directory>tests/**/Command/*.php</directory> |
| 121 | +``` |
| 122 | + |
| 123 | +Is _probably_ a user mistake and would translate to: `tests/**/Command/*.php/` |
| 124 | +(they'd probably intended `<directory suffix=".php">tests/**/Command`). |
| 125 | + |
| 126 | + |
0 commit comments