• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / phpstan-config / 22548915613

01 Mar 2026 05:43PM UTC coverage: 96.654% (-0.6%) from 97.276%
22548915613

push

github

web-flow
Merge pull request #131 from eliashaeussler/renovate/lock-file-maintenance

260 of 269 relevant lines covered (96.65%)

8.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.37
/src/Config/Config.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/phpstan-config".
7
 *
8
 * Copyright (C) 2023-2026 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\PHPStanConfig\Config;
25

26
use Closure;
27
use EliasHaeussler\PHPStanConfig\Enums;
28
use EliasHaeussler\PHPStanConfig\Exception;
29
use EliasHaeussler\PHPStanConfig\Resource;
30
use EliasHaeussler\PHPStanConfig\Rule;
31
use EliasHaeussler\PHPStanConfig\Set;
32
use ReflectionFunction;
33
use ReflectionNamedType;
34

35
use function array_map;
36
use function is_a;
37
use function preg_quote;
38
use function sprintf;
39
use function str_starts_with;
40

41
/**
42
 * Config.
43
 *
44
 * @author Elias Häußler <elias@haeussler.dev>
45
 * @license GPL-3.0-or-later
46
 */
47
final class Config
48
{
49
    /**
50
     * @var list<non-empty-string>
51
     */
52
    private array $includes = [];
53

54
    /**
55
     * @var list<Set\Set>
56
     */
57
    private array $sets = [];
58

59
    private function __construct(
35✔
60
        private readonly Resource\Path $projectPath,
61
        public readonly Resource\Collection $parameters,
62
    ) {}
35✔
63

64
    /**
65
     * @param non-empty-string $projectDirectory
66
     */
67
    public static function create(string $projectDirectory): self
35✔
68
    {
69
        return new self(
35✔
70
            new Resource\Path($projectDirectory),
35✔
71
            Resource\Collection::create(),
35✔
72
        );
35✔
73
    }
74

75
    /**
76
     * @template T of Set\Set
77
     *
78
     * @param Closure(T, self): void|class-string<T> $configuratorOrClassName
79
     *
80
     * @throws Exception\SetConfiguratorIsNotValid
81
     */
82
    public function withSet(Closure|string $configuratorOrClassName): self
2✔
83
    {
84
        if ($configuratorOrClassName instanceof Closure) {
2✔
85
            $reflection = new ReflectionFunction($configuratorOrClassName);
1✔
86
            $type = $reflection->getParameters()[0]->getType();
1✔
87

88
            if (!$type instanceof ReflectionNamedType) {
1✔
89
                throw new Exception\SetConfiguratorIsNotValid();
×
90
            }
91

92
            $className = $type->getName();
1✔
93
            $configurator = $configuratorOrClassName;
1✔
94
        } else {
95
            $className = $configuratorOrClassName;
1✔
96
            $configurator = null;
1✔
97
        }
98

99
        if (!is_a($className, Set\Set::class, true)) {
2✔
100
            throw new Exception\SetConfiguratorIsNotValid();
×
101
        }
102

103
        /** @var T $set */
104
        $set = $className::create();
2✔
105

106
        if ($set instanceof Set\PathAwareSet) {
2✔
107
            $set->setProjectPath($this->projectPath);
2✔
108
        }
109

110
        if (null !== $configurator) {
2✔
111
            $configurator($set, $this);
1✔
112
        }
113

114
        $this->withSets($set);
2✔
115

116
        return $this;
2✔
117
    }
118

119
    public function withSets(Set\Set ...$sets): self
3✔
120
    {
121
        foreach ($sets as $set) {
3✔
122
            $this->sets[] = $set;
3✔
123
        }
124

125
        return $this;
3✔
126
    }
127

128
    /**
129
     * @param class-string<Rule\CustomRule>|non-empty-string $rule
130
     */
131
    public function withRule(string $rule): self
2✔
132
    {
133
        if (is_a($rule, Rule\CustomRule::class, true)) {
2✔
134
            $rule = $rule::getIdentifier();
1✔
135
        }
136

137
        $this->parameters->set($rule.'/enabled', true);
2✔
138

139
        return $this;
2✔
140
    }
141

142
    /**
143
     * @param class-string<Rule\CustomRule>|non-empty-string $rule
144
     */
145
    public function withoutRule(string $rule): self
2✔
146
    {
147
        if (is_a($rule, Rule\CustomRule::class, true)) {
2✔
148
            $rule = $rule::getIdentifier();
1✔
149
        }
150

151
        $this->parameters->set($rule.'/enabled', false);
2✔
152

153
        return $this;
2✔
154
    }
155

156
    /**
157
     * @param non-empty-string ...$paths
158
     *
159
     * @see https://phpstan.org/config-reference#analysed-files
160
     */
161
    public function in(string ...$paths): self
1✔
162
    {
163
        $paths = array_map($this->projectPath->resolve(...), $paths);
1✔
164

165
        $this->parameters->add('paths', ...$paths);
1✔
166

167
        return $this;
1✔
168
    }
169

170
    /**
171
     * @param non-empty-string ...$paths
172
     *
173
     * @see https://phpstan.org/config-reference#analysed-files
174
     */
175
    public function not(string ...$paths): self
1✔
176
    {
177
        $paths = array_map($this->projectPath->resolve(...), $paths);
1✔
178

179
        $this->parameters->add('excludePaths/analyseAndScan', ...$paths);
1✔
180

181
        return $this;
1✔
182
    }
183

184
    /**
185
     * @param int<0, 10> $level
186
     *
187
     * @see https://phpstan.org/config-reference#rule-level
188
     */
189
    public function level(int $level): self
1✔
190
    {
191
        $this->parameters->set('level', $level);
1✔
192

193
        return $this;
1✔
194
    }
195

196
    /**
197
     * @see https://phpstan.org/user-guide/rule-levels
198
     */
199
    public function maxLevel(): self
1✔
200
    {
201
        $this->parameters->set('level', 'max');
1✔
202

203
        return $this;
1✔
204
    }
205

206
    /**
207
     * @param array<non-empty-string, bool> $featureToggles
208
     *
209
     * @see https://phpstan.org/blog/what-is-bleeding-edge
210
     */
211
    public function withBleedingEdge(array $featureToggles = []): self
2✔
212
    {
213
        foreach ($featureToggles as $name => $value) {
2✔
214
            $this->parameters->set('featureToggles/'.$name, $value);
1✔
215
        }
216

217
        return $this->with('phar://phpstan.phar/conf/bleedingEdge.neon');
2✔
218
    }
219

220
    /**
221
     * @param non-empty-string $file
222
     *
223
     * @see https://phpstan.org/user-guide/baseline
224
     */
225
    public function withBaseline(string $file = 'phpstan-baseline.neon'): self
1✔
226
    {
227
        return $this->with($file);
1✔
228
    }
229

230
    /**
231
     * @param non-empty-string ...$files
232
     *
233
     * @see https://phpstan.org/config-reference#multiple-files
234
     */
235
    public function with(string ...$files): self
4✔
236
    {
237
        foreach ($files as $file) {
4✔
238
            $this->includes[] = $this->projectPath->resolve($file);
4✔
239
        }
240

241
        return $this;
4✔
242
    }
243

244
    /**
245
     * @param non-empty-string ...$files
246
     *
247
     * @see https://phpstan.org/config-reference#bootstrap
248
     */
249
    public function bootstrapFiles(string ...$files): self
1✔
250
    {
251
        $paths = array_map($this->projectPath->resolve(...), $files);
1✔
252

253
        $this->parameters->add('bootstrapFiles', ...$paths);
1✔
254

255
        return $this;
1✔
256
    }
257

258
    /**
259
     * @param non-empty-string ...$files
260
     *
261
     * @see https://phpstan.org/config-reference#stub-files
262
     */
263
    public function stubFiles(string ...$files): self
1✔
264
    {
265
        $paths = array_map($this->projectPath->resolve(...), $files);
1✔
266

267
        $this->parameters->add('stubFiles', ...$paths);
1✔
268

269
        return $this;
1✔
270
    }
271

272
    /**
273
     * @param non-empty-string $cacheDir
274
     *
275
     * @see https://phpstan.org/config-reference#caching
276
     */
277
    public function useCacheDir(string $cacheDir): self
1✔
278
    {
279
        $this->parameters->set('tmpDir', $this->projectPath->resolve($cacheDir));
1✔
280

281
        return $this;
1✔
282
    }
283

284
    /**
285
     * @param non-empty-string|null $message
286
     * @param non-empty-string|null $path
287
     * @param positive-int|null     $count
288
     * @param non-empty-string|null $identifier
289
     *
290
     * @throws Exception\IgnoreErrorEntryIsNotValid
291
     *
292
     * @see https://phpstan.org/config-reference#ignoring-errors
293
     */
294
    public function ignoreError(
10✔
295
        ?string $message = null,
296
        ?string $path = null,
297
        ?int $count = null,
298
        ?bool $reportUnmatched = null,
299
        ?string $identifier = null,
300
    ): self {
301
        if (null === $message && null === $identifier) {
10✔
302
            throw new Exception\IgnoreErrorEntryIsNotValid();
1✔
303
        }
304

305
        // Convert plain message to regex
306
        if (null !== $message && !str_starts_with($message, '#')) {
9✔
307
            $message = sprintf('#^%s$#', preg_quote($message, '#'));
7✔
308
        }
309

310
        $error = [];
9✔
311

312
        if (null !== $message) {
9✔
313
            $error['message'] = $message;
8✔
314
        }
315
        if (null !== $path) {
9✔
316
            $error['path'] = $this->projectPath->resolve($path);
5✔
317
        }
318
        if (null !== $count) {
9✔
319
            $error['count'] = $count;
3✔
320
        }
321
        if (null !== $reportUnmatched) {
9✔
322
            $error['reportUnmatched'] = $reportUnmatched;
2✔
323
        }
324
        if (null !== $identifier) {
9✔
325
            $error['identifier'] = $identifier;
2✔
326
        }
327

328
        $this->parameters->add('ignoreErrors', $error);
9✔
329

330
        return $this;
9✔
331
    }
332

333
    /**
334
     * @see https://phpstan.org/user-guide/ignoring-errors#reporting-unused-ignores
335
     */
336
    public function reportUnmatchedIgnoredErrors(bool $enable = true): self
1✔
337
    {
338
        $this->parameters->set('reportUnmatchedIgnoredErrors', $enable);
1✔
339

340
        return $this;
1✔
341
    }
342

343
    /**
344
     * @see https://phpstan.org/config-reference#errorformat
345
     */
346
    public function formatAs(Enums\ErrorFormat $format): self
1✔
347
    {
348
        $this->parameters->set('errorFormat', $format->value);
1✔
349

350
        return $this;
1✔
351
    }
352

353
    /**
354
     * @see https://phpstan.org/config-reference#treatphpdoctypesascertain
355
     */
356
    public function treatPhpDocTypesAsCertain(bool $enable = true): self
1✔
357
    {
358
        $this->parameters->set('treatPhpDocTypesAsCertain', $enable);
1✔
359

360
        return $this;
1✔
361
    }
362

363
    /**
364
     * @see https://phpstan.org/config-reference#exceptions
365
     */
366
    public function checkTooWideThrowTypes(bool $enable = true): self
1✔
367
    {
368
        $this->parameters->set('exceptions/check/tooWideThrowType', $enable);
1✔
369

370
        return $this;
1✔
371
    }
372

373
    /**
374
     * @see https://phpstan.org/config-reference#exceptions
375
     */
376
    public function checkMissingCheckedExceptionInThrows(bool $enable = true): self
1✔
377
    {
378
        $this->parameters->set('exceptions/check/missingCheckedExceptionInThrows', $enable);
1✔
379

380
        return $this;
1✔
381
    }
382

383
    /**
384
     * @see https://phpstan.org/config-reference#exceptions
385
     */
386
    public function reportUncheckedExceptionDeadCatch(bool $enable = true): self
1✔
387
    {
388
        $this->parameters->set('exceptions/reportUncheckedExceptionDeadCatch', $enable);
1✔
389

390
        return $this;
1✔
391
    }
392

393
    public function useCustomRule(string $identifier, bool $enable = true): self
1✔
394
    {
395
        $this->parameters->set($identifier.'/enabled', $enable);
1✔
396

397
        return $this;
1✔
398
    }
399

400
    /**
401
     * @return array{
402
     *     includes: list<non-empty-string>,
403
     *     parameters: array<non-empty-string, mixed>,
404
     * }
405
     */
406
    public function toArray(): array
34✔
407
    {
408
        $parameters = $this->parameters;
34✔
409

410
        foreach ($this->sets as $set) {
34✔
411
            if ($set instanceof Set\ParameterizableSet) {
3✔
412
                $parameters = $parameters->merge($set->getParameters());
3✔
413
            }
414
        }
415

416
        return [
34✔
417
            'includes' => $this->includes,
34✔
418
            'parameters' => $parameters->toArray(),
34✔
419
        ];
34✔
420
    }
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc