Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConfigBuilder
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 29
1260
0.00% covered (danger)
0.00%
0 / 1
 setRawOptions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 make
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFileList
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addFiles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludedFileList
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 excludeFiles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludeFileRegex
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setDirectoryList
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addDirectories
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludedDirectoryList
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 excludeDirectories
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setMinimumSeverity
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 allowMissingProperties
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 allowScalarImplicitCasts
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 allowNullCastsAsAnyType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 enableDeadCodeDetection
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 shouldDeadCodeDetectionPreferFalseNegatives
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setProgressBarMode
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 setSuppressedIssuesList
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 suppressIssueTypes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addGlobalsWithTypes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 readClassAliases
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 enableRedundantConditionDetection
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setMinimumPHPVersion
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTargetPHPVersion
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addPlugins
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addCustomPlugins
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 enableTaintCheck
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 getTaintCheckPluginName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/** @noinspection PhpUnused */
3
4namespace MediaWikiPhanConfig;
5
6use InvalidArgumentException;
7
8class ConfigBuilder {
9    public const PROGRESS_BAR_DISABLED = 0;
10    public const PROGRESS_BAR_STANDARD = 1;
11    public const PROGRESS_BAR_LONG = 2;
12
13    /** @var array */
14    private array $options = [];
15
16    /**
17     * Sets an array of raw phan options. This should generally be avoided, in favour of the setters below.
18     * @param array $options
19     * @return self
20     */
21    public function setRawOptions( array $options ): self {
22        $this->options = array_replace( $this->options, $options );
23        return $this;
24    }
25
26    /**
27     * @return array
28     */
29    public function make(): array {
30        return $this->options;
31    }
32
33    /**
34     * @param array $list
35     * @return $this
36     */
37    public function setFileList( array $list ): self {
38        $this->options['file_list'] = $list;
39        return $this;
40    }
41
42    /**
43     * @param string ...$files
44     * @return $this
45     */
46    public function addFiles( string ...$files ): self {
47        $this->options['file_list'] = array_merge(
48            $this->options['file_list'] ?? [],
49            $files
50        );
51        return $this;
52    }
53
54    /**
55     * @param array $list
56     * @return $this
57     */
58    public function setExcludedFileList( array $list ): self {
59        $this->options['exclude_file_list'] = $list;
60        return $this;
61    }
62
63    /**
64     * @param string ...$files
65     * @return $this
66     */
67    public function excludeFiles( string ...$files ): self {
68        $this->options['exclude_file_list'] = array_merge(
69            $this->options['exclude_file_list'] ?? [],
70            $files
71        );
72        return $this;
73    }
74
75    /**
76     * @param string $regex
77     * @return $this
78     */
79    public function setExcludeFileRegex( string $regex ): self {
80        $this->options['exclude_file_regex'] = $regex;
81        return $this;
82    }
83
84    /**
85     * @param array $list
86     * @return $this
87     */
88    public function setDirectoryList( array $list ): self {
89        $this->options['directory_list'] = $list;
90        return $this;
91    }
92
93    /**
94     * @param string ...$dirs
95     * @return $this
96     */
97    public function addDirectories( string ...$dirs ): self {
98        $this->options['directory_list'] = array_merge(
99            $this->options['directory_list'] ?? [],
100            $dirs
101        );
102        return $this;
103    }
104
105    /**
106     * @param array $list
107     * @return $this
108     */
109    public function setExcludedDirectoryList( array $list ): self {
110        $this->options['exclude_analysis_directory_list'] = $list;
111        return $this;
112    }
113
114    /**
115     * @param string ...$dirs
116     * @return $this
117     */
118    public function excludeDirectories( string ...$dirs ): self {
119        $this->options['exclude_analysis_directory_list'] = array_merge(
120            $this->options['exclude_analysis_directory_list'] ?? [],
121            $dirs
122        );
123        return $this;
124    }
125
126    /**
127     * @param int $minSev
128     * @return $this
129     */
130    public function setMinimumSeverity( int $minSev ): self {
131        $this->options['minimum_severity'] = $minSev;
132        return $this;
133    }
134
135    /**
136     * @param bool $yn
137     * @return $this
138     */
139    public function allowMissingProperties( bool $yn ): self {
140        $this->options['allow_missing_properties'] = $yn;
141        return $this;
142    }
143
144    /**
145     * @param bool $yn
146     * @return $this
147     */
148    public function allowScalarImplicitCasts( bool $yn ): self {
149        $this->options['scalar_implicit_cast'] = $yn;
150        return $this;
151    }
152
153    /**
154     * @param bool $yn
155     * @return $this
156     */
157    public function allowNullCastsAsAnyType( bool $yn ): self {
158        $this->options['null_casts_as_any_type'] = $yn;
159        return $this;
160    }
161
162    /**
163     * @param bool $yn
164     * @return $this
165     */
166    public function enableDeadCodeDetection( bool $yn ): self {
167        $this->options['dead_code_detection'] = $yn;
168        return $this;
169    }
170
171    /**
172     * @param bool $yn
173     * @return $this
174     */
175    public function shouldDeadCodeDetectionPreferFalseNegatives( bool $yn ): self {
176        $this->options['dead_code_detection_prefer_false_negative'] = $yn;
177        return $this;
178    }
179
180    /**
181     * @param int $mode One of the PROGRESS_BAR_* constants
182     * @return $this
183     */
184    public function setProgressBarMode( int $mode ): self {
185        switch ( $mode ) {
186            case self::PROGRESS_BAR_DISABLED:
187                $this->options['progress_bar'] = false;
188                break;
189            case self::PROGRESS_BAR_STANDARD:
190                $this->options['progress_bar'] = true;
191                break;
192            case self::PROGRESS_BAR_LONG:
193                $this->options['progress_bar'] = true;
194                $this->options['long_progress_bar'] = true;
195                break;
196            default:
197                throw new InvalidArgumentException( "Invalid $mode" );
198        }
199        return $this;
200    }
201
202    /**
203     * @param array $list
204     * @return $this
205     */
206    public function setSuppressedIssuesList( array $list ): self {
207        $this->options['suppress_issue_types'] = $list;
208        return $this;
209    }
210
211    /**
212     * @param string ...$types
213     * @return $this
214     */
215    public function suppressIssueTypes( string ...$types ): self {
216        $this->options['suppress_issue_types'] = array_merge(
217            $this->options['suppress_issue_types'] ?? [],
218            $types
219        );
220        return $this;
221    }
222
223    /**
224     * @param array $globals [ 'global_name' => 'union_type' ]
225     * @return $this
226     */
227    public function addGlobalsWithTypes( array $globals ): self {
228        $this->options['globals_type_map'] = array_merge(
229            $this->options['globals_type_map'] ?? [],
230            $globals
231        );
232        return $this;
233    }
234
235    /**
236     * @param bool $yn
237     * @return $this
238     */
239    public function readClassAliases( bool $yn ): self {
240        $this->options['enable_class_alias_support'] = $yn;
241        return $this;
242    }
243
244    /**
245     * @param bool $yn
246     * @return $this
247     */
248    public function enableRedundantConditionDetection( bool $yn ): self {
249        $this->options['redundant_condition_detection'] = $yn;
250        return $this;
251    }
252
253    /**
254     * Set the minimum PHP version that the codebase should support.
255     * @param string $version
256     * @return $this
257     */
258    public function setMinimumPHPVersion( string $version ): self {
259        $this->options['minimum_target_php_version'] = $version;
260        return $this;
261    }
262
263    /**
264     * Set the PHP version to be checked against for forward-compatibility warnings.
265     * @param string $version
266     * @return $this
267     */
268    public function setTargetPHPVersion( string $version ): self {
269        $this->options['target_php_version'] = $version;
270        return $this;
271    }
272
273    /**
274     * Adds one or more built-in plugins.
275     * @param string[] $plugins
276     * @return self
277     */
278    public function addPlugins( array $plugins ): self {
279        $this->options['plugins'] = array_merge(
280            $this->options['plugins'] ?? [],
281            $plugins
282        );
283        return $this;
284    }
285
286    /**
287     * Adds one or more custom plugins.
288     *
289     * @param string[] $plugins
290     * @return self
291     */
292    public function addCustomPlugins( array $plugins ): self {
293        foreach ( $plugins as $plugin ) {
294            $this->options['plugins'][] = __DIR__ . "/Plugin/$plugin.php";
295        }
296        return $this;
297    }
298
299    /**
300     * @internal
301     * This should only be used by the config file in this repo.
302     *
303     * @param string $curDir
304     * @param string $vendorPath
305     * @return $this
306     */
307    public function enableTaintCheck(
308        string $curDir,
309        string $vendorPath
310    ): self {
311        $taintCheckPluginName = $this->getTaintCheckPluginName();
312        $taintCheckPath = $curDir . "/../../phan-taint-check-plugin/$taintCheckPluginName.php";
313        if ( !file_exists( $taintCheckPath ) ) {
314            $taintCheckPath =
315                "$vendorPath/vendor/mediawiki/phan-taint-check-plugin/$taintCheckPluginName.php";
316        }
317        $this->options['plugins'][] = $taintCheckPath;
318        // Taint-check specific settings. NOTE: don't remove these lines, even if they duplicate some of
319        // the settings above. taint-check may fail hard if one of these settings goes missing.
320        $this->options['quick_mode'] = false;
321        $this->options['suppress_issue_types'] = array_merge(
322            $this->options['suppress_issue_types'],
323            [
324                // We obviously don't want to report false positives
325                'SecurityCheck-LikelyFalsePositive',
326                // This one still has a lot of false positives
327                'SecurityCheck-PHPSerializeInjection',
328            ]
329        );
330        return $this;
331    }
332
333    protected function getTaintCheckPluginName(): string {
334        return 'GenericSecurityCheckPlugin';
335    }
336}