1717use function json_decode ;
1818use function json_last_error ;
1919use function json_last_error_msg ;
20+ use function preg_quote ;
21+ use function preg_replace ;
22+ use function preg_replace_callback ;
23+ use function realpath ;
24+ use function str_replace ;
2025use function strpos ;
26+ use function strtr ;
27+ use function trim ;
2128use const ARRAY_FILTER_USE_KEY ;
2229use const JSON_ERROR_NONE ;
2330
@@ -46,6 +53,14 @@ class ComposerJson
4653 */
4754 public $ autoloadPaths ;
4855
56+ /**
57+ * Regex => isDev
58+ *
59+ * @readonly
60+ * @var array<string, bool>
61+ */
62+ public $ autoloadExcludeRegexes ;
63+
4964 /**
5065 * @throws InvalidPathException
5166 * @throws InvalidConfigException
@@ -72,6 +87,10 @@ public function __construct(
7287 $ this ->extractAutoloadPaths ($ basePath , $ composerJsonData ['autoload-dev ' ]['files ' ] ?? [], true ),
7388 $ this ->extractAutoloadPaths ($ basePath , $ composerJsonData ['autoload-dev ' ]['classmap ' ] ?? [], true )
7489 );
90+ $ this ->autoloadExcludeRegexes = array_merge (
91+ $ this ->extractAutoloadExcludeRegexes ($ basePath , $ composerJsonData ['autoload ' ]['exclude-from-classmap ' ] ?? [], false ),
92+ $ this ->extractAutoloadExcludeRegexes ($ basePath , $ composerJsonData ['autoload-dev ' ]['exclude-from-classmap ' ] ?? [], true )
93+ );
7594
7695 $ filterPackages = static function (string $ package ): bool {
7796 return strpos ($ package , '/ ' ) !== false ;
@@ -125,6 +144,76 @@ private function extractAutoloadPaths(string $basePath, array $autoload, bool $i
125144 return $ result ;
126145 }
127146
147+ /**
148+ * @param array<string> $exclude
149+ * @return array<string, bool>
150+ * @throws InvalidPathException
151+ */
152+ private function extractAutoloadExcludeRegexes (string $ basePath , array $ exclude , bool $ isDev ): array
153+ {
154+ $ regexes = [];
155+
156+ foreach ($ exclude as $ path ) {
157+ $ regexes [$ this ->resolveAutoloadExclude ($ basePath , $ path )] = $ isDev ;
158+ }
159+
160+ return $ regexes ;
161+ }
162+
163+ /**
164+ * Implementation copied from composer/composer.
165+ *
166+ * @license MIT https://github.com/composer/composer/blob/ee2c9afdc86ef3f06a4bd49b1fea7d1d636afc92/LICENSE
167+ * @see https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps
168+ * @see https://github.com/composer/composer/blob/ee2c9afdc86ef3f06a4bd49b1fea7d1d636afc92/src/Composer/Autoload/AutoloadGenerator.php#L1256-L1286
169+ * @throws InvalidPathException
170+ */
171+ private function resolveAutoloadExclude (string $ basePath , string $ pathPattern ): string
172+ {
173+ // first escape user input
174+ $ path = preg_replace ('{/+} ' , '/ ' , preg_quote (trim (strtr ($ pathPattern , '\\' , '/ ' ), '/ ' )));
175+
176+ if ($ path === null ) {
177+ throw new InvalidPathException ("Failure while globbing $ pathPattern path. " );
178+ }
179+
180+ // add support for wildcards * and **
181+ $ path = strtr ($ path , ['\\* \\* ' => '.+? ' , '\\* ' => '[^/]+? ' ]);
182+
183+ // add support for up-level relative paths
184+ $ updir = null ;
185+ $ path = preg_replace_callback (
186+ '{^((?:(?: \\\\\\.){1,2}+/)+)} ' ,
187+ static function ($ matches ) use (&$ updir ): string {
188+ if (isset ($ matches [1 ]) && $ matches [1 ] !== '' ) {
189+ // undo preg_quote for the matched string
190+ $ updir = str_replace ('\\. ' , '. ' , $ matches [1 ]);
191+ }
192+
193+ return '' ;
194+ },
195+ $ path
196+ // note: composer also uses `PREG_UNMATCHED_AS_NULL` but the `$flags` arg supported since PHP v7.4
197+ );
198+
199+ if ($ path === null ) {
200+ throw new InvalidPathException ("Failure while globbing $ pathPattern path. " );
201+ }
202+
203+ $ resolvedPath = realpath ($ basePath . '/ ' . $ updir );
204+
205+ if ($ resolvedPath === false ) {
206+ throw new InvalidPathException ("Failure while globbing $ pathPattern path. " );
207+ }
208+
209+ // Finalize
210+ $ delimiter = '# ' ;
211+ $ pattern = '^ ' . preg_quote (strtr ($ resolvedPath , '\\' , '/ ' ), $ delimiter ) . '/ ' . $ path . '($|/) ' ;
212+ $ pattern = $ delimiter . $ pattern . $ delimiter ;
213+
214+ return $ pattern ;
215+ }
216+
128217 /**
129218 * @return array{
130219 * require?: array<string, string>,
@@ -136,13 +225,15 @@ private function extractAutoloadPaths(string $basePath, array $autoload, bool $i
136225 * psr-0?: array<string, string|string[]>,
137226 * psr-4?: array<string, string|string[]>,
138227 * files?: string[],
139- * classmap?: string[]
228+ * classmap?: string[],
229+ * exclude-from-classmap?: string[]
140230 * },
141231 * autoload-dev?: array{
142232 * psr-0?: array<string, string|string[]>,
143233 * psr-4?: array<string, string|string[]>,
144234 * files?: string[],
145- * classmap?: string[]
235+ * classmap?: string[],
236+ * exclude-from-classmap?: string[]
146237 * }
147238 * }
148239 * @throws InvalidPathException
0 commit comments