22
33declare (strict_types=1 );
44
5- namespace TypePHP \Tests \Contract ;
6-
7- use ReflectionMethod ;
85use TypePHP \Contract \FileFilter ;
96use TypePHP \Internal \Config ;
107use TypePHP \Internal \StreamWrapper ;
3633 StreamWrapper::register ();
3734
3835 $ projectRoot = Config::getProjectRoot ();
36+
3937 $ vendorDoctrineFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/doctrine/dbal/src/Schema/AbstractNamedObject.php ' );
4038 $ appFile = str_replace ('\\' , '/ ' , $ projectRoot . '/app/Services/UserService.php ' );
4139
5351 Config::set ([
5452 'include ' => [
5553 'src/** ' ,
56- 'vendor/my-org/whitelisted-package/** ' ,
54+ 'vendor/my-org/whitelisted-package/** ' ,
5755 ],
5856 'exclude ' => [
5957 'vendor/** ' ,
8078 Config::set ([
8179 'include ' => [
8280 'src/** ' ,
83- 'src/Core/Framework/** ' ,
81+ 'src/Core/Framework/** ' ,
8482 'src/Core/Content/** ' ,
8583 ],
8684 'exclude ' => [
106104 ]);
107105
108106 StreamWrapper::register ();
107+
109108 $ projectRoot = Config::getProjectRoot ();
110109
111110 $ appLibFile = str_replace ('\\' , '/ ' , $ projectRoot . '/lib/Services/PaymentProcessor.php ' );
112111 $ vendorLibFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/dompdf/php-font-lib/lib/Font.php ' );
112+
113113 expect (FileFilter::isFileExcluded ($ appLibFile ))->toBeFalse ();
114114 expect (FileFilter::isFileExcluded ($ vendorLibFile ))->toBeTrue ();
115115
118118 ->and ($ refMethod ->invoke (null , $ vendorLibFile , $ vendorLibFile ))->toBeFalse ()
119119 ;
120120 });
121- });
121+
122+ test ('allows whitelisting a single specific file inside a vendor package while excluding its siblings ' , function () {
123+ Config::set ([
124+ 'include ' => [
125+ 'src/** ' ,
126+ 'vendor/monolog/monolog/src/Monolog/Logger.php ' ,
127+ ],
128+ 'exclude ' => [
129+ 'vendor/** ' ,
130+ ],
131+ ]);
132+
133+ StreamWrapper::register ();
134+
135+ $ projectRoot = Config::getProjectRoot ();
136+
137+ $ whitelistedSingleFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/monolog/monolog/src/Monolog/Logger.php ' );
138+ $ siblingVendorFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php ' );
139+
140+ expect (FileFilter::isFileExcluded ($ whitelistedSingleFile ))->toBeFalse ();
141+ expect (FileFilter::isFileExcluded ($ siblingVendorFile ))->toBeTrue ();
142+
143+ $ refMethod = new ReflectionMethod (StreamWrapper::class, 'isApplicationFile ' );
144+ expect ($ refMethod ->invoke (null , $ whitelistedSingleFile , $ whitelistedSingleFile ))->toBeTrue ()
145+ ->and ($ refMethod ->invoke (null , $ siblingVendorFile , $ siblingVendorFile ))->toBeFalse ()
146+ ;
147+ });
148+
149+ test ('allows blacklisting a specific legacy file inside an otherwise whitelisted vendor package ' , function () {
150+ Config::set ([
151+ 'include ' => [
152+ 'src/** ' ,
153+ 'vendor/acme/custom-package/** ' ,
154+ ],
155+ 'exclude ' => [
156+ 'vendor/** ' ,
157+ 'vendor/acme/custom-package/src/Legacy/UnsafeFile.php ' ,
158+ ],
159+ ]);
160+
161+ StreamWrapper::register ();
162+
163+ $ projectRoot = Config::getProjectRoot ();
164+
165+ $ safeFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/acme/custom-package/src/SafeService.php ' );
166+ $ unsafeFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/acme/custom-package/src/Legacy/UnsafeFile.php ' );
167+
168+ expect (FileFilter::isFileExcluded ($ safeFile ))->toBeFalse ();
169+ expect (FileFilter::isFileExcluded ($ unsafeFile ))->toBeTrue ();
170+
171+ $ refMethod = new ReflectionMethod (StreamWrapper::class, 'isApplicationFile ' );
172+ expect ($ refMethod ->invoke (null , $ safeFile , $ safeFile ))->toBeTrue ()
173+ ->and ($ refMethod ->invoke (null , $ unsafeFile , $ unsafeFile ))->toBeFalse ()
174+ ;
175+ });
176+
177+ test ('does not falsely classify application directories like vendor-tools/ or vendor_custom/ as vendor directories ' , function () {
178+ Config::set ([
179+ 'include ' => [
180+ 'vendor-tools/** ' ,
181+ 'vendor_custom/** ' ,
182+ 'src/** ' ,
183+ ],
184+ 'exclude ' => [
185+ 'vendor/** ' ,
186+ ],
187+ ]);
188+
189+ StreamWrapper::register ();
190+
191+ $ projectRoot = Config::getProjectRoot ();
192+
193+ $ appToolsFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor-tools/DeployScript.php ' );
194+ $ appCustomFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor_custom/Helper.php ' );
195+ $ realVendorFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/symfony/console/Application.php ' );
196+
197+ expect (FileFilter::isFileExcluded ($ appToolsFile ))->toBeFalse ();
198+ expect (FileFilter::isFileExcluded ($ appCustomFile ))->toBeFalse ();
199+ expect (FileFilter::isFileExcluded ($ realVendorFile ))->toBeTrue ();
200+
201+ $ refMethod = new ReflectionMethod (StreamWrapper::class, 'isApplicationFile ' );
202+ expect ($ refMethod ->invoke (null , $ appToolsFile , $ appToolsFile ))->toBeTrue ()
203+ ->and ($ refMethod ->invoke (null , $ appCustomFile , $ appCustomFile ))->toBeTrue ()
204+ ->and ($ refMethod ->invoke (null , $ realVendorFile , $ realVendorFile ))->toBeFalse ()
205+ ;
206+ });
207+
208+ test ('handles vendor package names containing hyphens, dots, numbers, and scoped prefixes ' , function () {
209+ Config::set ([
210+ 'include ' => [
211+ 'src/** ' ,
212+ 'vendor/symfony/polyfill-php83/** ' ,
213+ 'vendor/2amigos/qrcode-library/** ' ,
214+ ],
215+ 'exclude ' => [
216+ 'vendor/** ' ,
217+ ],
218+ ]);
219+
220+ StreamWrapper::register ();
221+
222+ $ projectRoot = Config::getProjectRoot ();
223+
224+ $ scopedVendorFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/symfony/polyfill-php83/bootstrap.php ' );
225+ $ numericVendorFile = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/2amigos/qrcode-library/src/QrCode.php ' );
226+ $ unwhitelistedVendor = str_replace ('\\' , '/ ' , $ projectRoot . '/vendor/guzzlehttp/guzzle/src/Client.php ' );
227+
228+ expect (FileFilter::isFileExcluded ($ scopedVendorFile ))->toBeFalse ();
229+ expect (FileFilter::isFileExcluded ($ numericVendorFile ))->toBeFalse ();
230+ expect (FileFilter::isFileExcluded ($ unwhitelistedVendor ))->toBeTrue ();
231+ });
232+
233+ test ('handles mixed Windows backslashes and Unix forward slashes in vendor paths seamlessly ' , function () {
234+ Config::set ([
235+ 'include ' => [
236+ 'src/** ' ,
237+ ],
238+ 'exclude ' => [
239+ 'vendor/** ' ,
240+ ],
241+ ]);
242+
243+ StreamWrapper::register ();
244+
245+ $ projectRoot = Config::getProjectRoot ();
246+
247+ $ windowsVendorPath = $ projectRoot . '\\vendor \\doctrine \\dbal \\src \\Schema \\Column.php ' ;
248+ $ windowsAppPath = $ projectRoot . '\\app \\Services \\OrderService.php ' ;
249+
250+ expect (FileFilter::isFileExcluded ($ windowsVendorPath ))->toBeTrue ()
251+ ->and (FileFilter::isFileExcluded ($ windowsAppPath ))->toBeFalse ()
252+ ;
253+
254+ $ refMethod = new ReflectionMethod (StreamWrapper::class, 'isApplicationFile ' );
255+ expect ($ refMethod ->invoke (null , $ windowsVendorPath , $ windowsVendorPath ))->toBeFalse ()
256+ ->and ($ refMethod ->invoke (null , $ windowsAppPath , $ windowsAppPath ))->toBeTrue ()
257+ ;
258+ });
259+ });
0 commit comments