1+ <?php
2+ /**
3+ * PHPCompatibility, an external standard for PHP_CodeSniffer.
4+ *
5+ * @package PHPCompatibility
6+ * @copyright 2012-2020 PHPCompatibility Contributors
7+ * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+ * @link https://github.com/PHPCompatibility/PHPCompatibility
9+ */
10+
11+ namespace PHPCompatibilityMagento \Sniffs \FunctionUse ;
12+
13+ use PHPCompatibility \AbstractFunctionCallParameterSniff ;
14+ use PHP_CodeSniffer \Files \File ;
15+
16+ /**
17+ * Detect missing required function parameters in calls to native PHP functions.
18+ *
19+ * Specifically when those function parameters used to be optional in older PHP versions.
20+ *
21+ * PHP version All
22+ *
23+ * @link https://www.php.net/manual/en/doc.changelog.php
24+ *
25+ * @since 8.1.0
26+ * @since 9.0.0 Renamed from `OptionalRequiredFunctionParametersSniff` to `OptionalToRequiredFunctionParametersSniff`.
27+ * @since 10.0.0 Now extends the base `AbstractFunctionCallParameterSniff` class.
28+ * Previously the sniff extended the sister-sniff `RequiredToOptionalFunctionParametersSniff`.
29+ * Methods which were previously required due to the extending of the (grand-parent)
30+ * `AbstractComplexVersionSniff` have been removed.
31+ */
32+ class OptionalToRequiredFunctionParametersSniff extends AbstractFunctionCallParameterSniff
33+ {
34+
35+ /**
36+ * A list of function parameters, which were optional in older versions and became required later on.
37+ *
38+ * The array lists : version number with true (required) and false (optional use deprecated).
39+ *
40+ * The index is the location of the parameter in the parameter list, starting at 0 !
41+ * If's sufficient to list the last version in which the parameter was not yet required.
42+ *
43+ * @since 8.1.0
44+ * @since 10.0.0 Parameter renamed from `$functionParameters` to `$targetFunctions` for
45+ * compatibility with the `AbstractFunctionCallParameterSniff` class.
46+ *
47+ * @var array
48+ */
49+ protected $ targetFunctions = [
50+ 'crypt ' => [
51+ 1 => [
52+ 'name ' => 'salt ' ,
53+ '5.6 ' => false ,
54+ '8.0 ' => true ,
55+ ],
56+ ],
57+ 'gmmktime ' => [
58+ 1 => [
59+ 'name ' => 'hour ' ,
60+ '8.0 ' => true ,
61+ ],
62+ ],
63+ 'mb_parse_str ' => [
64+ 1 => [
65+ 'name ' => 'result ' ,
66+ '8.0 ' => true ,
67+ ],
68+ ],
69+ 'mktime ' => [
70+ 0 => [
71+ 'name ' => 'hour ' ,
72+ '5.1 ' => false ,
73+ '8.0 ' => true ,
74+ ],
75+ ],
76+ 'openssl_seal ' => [
77+ 4 => [
78+ 'name ' => 'method ' ,
79+ '8.0 ' => true ,
80+ ],
81+ ],
82+ 'openssl_open ' => [
83+ 4 => [
84+ 'name ' => 'method ' ,
85+ '8.0 ' => true ,
86+ ],
87+ ],
88+ 'parse_str ' => [
89+ 1 => [
90+ 'name ' => 'result ' ,
91+ '7.2 ' => false ,
92+ '8.0 ' => true ,
93+ ],
94+ ],
95+ ];
96+
97+
98+ /**
99+ * Bowing out early is not applicable to this sniff.
100+ *
101+ * @since 10.0.0
102+ *
103+ * @return bool
104+ */
105+ protected function bowOutEarly ()
106+ {
107+ return false ;
108+ }
109+
110+ /**
111+ * Process the parameters of a matched function.
112+ *
113+ * @since 10.0.0 Part of the logic in this method was previously contained in the
114+ * parent sniff `process()` method (now removed).
115+ *
116+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
117+ * @param int $stackPtr The position of the current token in the stack.
118+ * @param string $functionName The token content (function name) which was matched.
119+ * @param array $parameters Array with information about the parameters.
120+ *
121+ * @return int|void Integer stack pointer to skip forward or void to continue
122+ * normal file processing.
123+ */
124+ public function processParameters (File $ phpcsFile , $ stackPtr , $ functionName , $ parameters )
125+ {
126+ $ functionLc = \strtolower ($ functionName );
127+ $ parameterCount = \count ($ parameters );
128+ $ parameterOffsetFound = $ parameterCount - 1 ;
129+
130+ foreach ($ this ->targetFunctions [$ functionLc ] as $ offset => $ parameterDetails ) {
131+ if ($ offset > $ parameterOffsetFound ) {
132+ $ itemInfo = [
133+ 'name ' => $ functionName ,
134+ 'nameLc ' => $ functionLc ,
135+ 'offset ' => $ offset ,
136+ ];
137+ $ this ->handleFeature ($ phpcsFile , $ stackPtr , $ itemInfo );
138+ }
139+ }
140+ }
141+
142+ /**
143+ * Process the function if no parameters were found.
144+ *
145+ * @since 10.0.0
146+ *
147+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
148+ * @param int $stackPtr The position of the current token in the stack.
149+ * @param string $functionName The token content (function name) which was matched.
150+ *
151+ * @return int|void Integer stack pointer to skip forward or void to continue
152+ * normal file processing.
153+ */
154+ public function processNoParameters (File $ phpcsFile , $ stackPtr , $ functionName )
155+ {
156+ $ this ->processParameters ($ phpcsFile , $ stackPtr , $ functionName , []);
157+ }
158+
159+ /**
160+ * Retrieve the relevant detail (version) information for use in an error message.
161+ *
162+ * @since 8.1.0
163+ * @since 10.0.0 - Method renamed from `getErrorInfo()` to `getVersionInfo().
164+ * - Second function parameter `$itemInfo` removed.
165+ * - Method visibility changed from `public` to `protected`.
166+ *
167+ * @param array $itemArray Version and other information about the item.
168+ *
169+ * @return array
170+ */
171+ protected function getVersionInfo (array $ itemArray )
172+ {
173+ $ versionInfo = [
174+ 'optionalDeprecated ' => '' ,
175+ 'optionalRemoved ' => '' ,
176+ 'error ' => false ,
177+ ];
178+
179+ foreach ($ itemArray as $ version => $ required ) {
180+ if (\preg_match ('`^\d\.\d(\.\d{1,2})?$` ' , $ version ) !== 1 ) {
181+ // Not a version key.
182+ continue ;
183+ }
184+
185+ if ($ this ->supportsAbove ($ version ) === true ) {
186+ if ($ required === true && $ versionInfo ['optionalRemoved ' ] === '' ) {
187+ $ versionInfo ['optionalRemoved ' ] = $ version ;
188+ $ versionInfo ['error ' ] = true ;
189+ } elseif ($ versionInfo ['optionalDeprecated ' ] === '' ) {
190+ $ versionInfo ['optionalDeprecated ' ] = $ version ;
191+ }
192+ }
193+ }
194+
195+ return $ versionInfo ;
196+ }
197+
198+ /**
199+ * Handle the retrieval of relevant information and - if necessary - throwing of an
200+ * error for a matched item.
201+ *
202+ * @since 10.0.0 This was previously handled via a similar method in the `AbstractComplexVersionSniff`.
203+ *
204+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
205+ * @param int $stackPtr The position of the relevant token in
206+ * the stack.
207+ * @param array $itemInfo Base information about the item.
208+ *
209+ * @return void
210+ */
211+ protected function handleFeature (File $ phpcsFile , $ stackPtr , array $ itemInfo )
212+ {
213+ $ itemArray = $ this ->targetFunctions [$ itemInfo ['nameLc ' ]][$ itemInfo ['offset ' ]];
214+ $ versionInfo = $ this ->getVersionInfo ($ itemArray );
215+
216+ if (empty ($ versionInfo ['optionalDeprecated ' ]) && empty ($ versionInfo ['optionalRemoved ' ])) {
217+ return ;
218+ }
219+
220+ $ this ->addError ($ phpcsFile , $ stackPtr , $ itemInfo , $ itemArray , $ versionInfo );
221+ }
222+
223+ /**
224+ * Generates the error or warning for this item.
225+ *
226+ * @since 8.1.0
227+ * @since 10.0.0 - Method visibility changed from `public` to `protected`.
228+ * - Introduced $itemArray parameter.
229+ * - Renamed the last parameter from `$errorInfo` to `$versionInfo`.
230+ *
231+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
232+ * @param int $stackPtr The position of the relevant token in
233+ * the stack.
234+ * @param array $itemInfo Base information about the item.
235+ * @param array $itemArray The sub-array with all the details about
236+ * this item.
237+ * @param array $versionInfo Array with detail (version) information
238+ * relevant to the item.
239+ *
240+ * @return void
241+ */
242+ protected function addError (File $ phpcsFile , $ stackPtr , array $ itemInfo , array $ itemArray , array $ versionInfo )
243+ {
244+ $ error = 'The "%s" parameter for function %s() is missing. Passing this parameter is no longer optional. The optional nature of the parameter is ' ;
245+ $ errorCode = $ this ->stringToErrorCode ($ itemInfo ['name ' ] . '_ ' . $ itemArray ['name ' ]);
246+ $ codeSuffix = '' ;
247+ $ data = [
248+ $ itemArray ['name ' ],
249+ $ itemInfo ['name ' ],
250+ ];
251+
252+ if ($ versionInfo ['optionalDeprecated ' ] !== '' ) {
253+ $ error .= 'deprecated since PHP %s and ' ;
254+ $ codeSuffix = 'Soft ' ;
255+ $ data [] = $ versionInfo ['optionalDeprecated ' ];
256+ }
257+
258+ if ($ versionInfo ['optionalRemoved ' ] !== '' ) {
259+ $ error .= 'removed since PHP %s and ' ;
260+ $ codeSuffix = 'Hard ' ;
261+ $ data [] = $ versionInfo ['optionalRemoved ' ];
262+ }
263+
264+ // Remove the last 'and' from the message.
265+ $ error = \substr ($ error , 0 , (\strlen ($ error ) - 5 ));
266+ $ errorCode .= $ codeSuffix . 'Required ' ;
267+
268+ $ this ->addMessage ($ phpcsFile , $ error , $ stackPtr , $ versionInfo ['error ' ], $ errorCode , $ data );
269+ }
270+ }
0 commit comments