@@ -17,197 +17,197 @@ |
||
17 | 17 | { |
18 | 18 | |
19 | 19 | |
20 | - /** |
|
21 | - * Creates a LocalFile object and sets the content. |
|
22 | - * |
|
23 | - * @param string $path The absolute path to the file. |
|
24 | - * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
25 | - * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
26 | - * |
|
27 | - * @return void |
|
28 | - */ |
|
29 | - public function __construct($path, Ruleset $ruleset, Config $config) |
|
30 | - { |
|
31 | - $this->path = trim($path); |
|
32 | - if (is_readable($this->path) === false) { |
|
33 | - parent::__construct($this->path, $ruleset, $config); |
|
34 | - $error = 'Error opening file; file no longer exists or you do not have access to read the file'; |
|
35 | - $this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false); |
|
36 | - $this->ignored = true; |
|
37 | - return; |
|
38 | - } |
|
39 | - |
|
40 | - // Before we go and spend time tokenizing this file, just check |
|
41 | - // to see if there is a tag up top to indicate that the whole |
|
42 | - // file should be ignored. It must be on one of the first two lines. |
|
43 | - if ($config->annotations === true) { |
|
44 | - $handle = fopen($this->path, 'r'); |
|
45 | - if ($handle !== false) { |
|
46 | - $firstContent = fgets($handle); |
|
47 | - $firstContent .= fgets($handle); |
|
48 | - fclose($handle); |
|
49 | - |
|
50 | - if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false |
|
51 | - || stripos($firstContent, 'phpcs:ignorefile') !== false |
|
52 | - ) { |
|
53 | - // We are ignoring the whole file. |
|
54 | - $this->ignored = true; |
|
55 | - return; |
|
56 | - } |
|
57 | - } |
|
58 | - } |
|
59 | - |
|
60 | - $this->reloadContent(); |
|
61 | - |
|
62 | - parent::__construct($this->path, $ruleset, $config); |
|
63 | - |
|
64 | - }//end __construct() |
|
65 | - |
|
66 | - |
|
67 | - /** |
|
68 | - * Loads the latest version of the file's content from the file system. |
|
69 | - * |
|
70 | - * @return void |
|
71 | - */ |
|
72 | - public function reloadContent() |
|
73 | - { |
|
74 | - $this->setContent(file_get_contents($this->path)); |
|
75 | - |
|
76 | - }//end reloadContent() |
|
77 | - |
|
78 | - |
|
79 | - /** |
|
80 | - * Processes the file. |
|
81 | - * |
|
82 | - * @return void |
|
83 | - */ |
|
84 | - public function process() |
|
85 | - { |
|
86 | - if ($this->ignored === true) { |
|
87 | - return; |
|
88 | - } |
|
89 | - |
|
90 | - if ($this->configCache['cache'] === false) { |
|
91 | - parent::process(); |
|
92 | - return; |
|
93 | - } |
|
94 | - |
|
95 | - $hash = md5_file($this->path); |
|
96 | - $cache = Cache::get($this->path); |
|
97 | - if ($cache !== false && $cache['hash'] === $hash) { |
|
98 | - // We can't filter metrics, so just load all of them. |
|
99 | - $this->metrics = $cache['metrics']; |
|
100 | - |
|
101 | - if ($this->configCache['recordErrors'] === true) { |
|
102 | - // Replay the cached errors and warnings to filter out the ones |
|
103 | - // we don't need for this specific run. |
|
104 | - $this->configCache['cache'] = false; |
|
105 | - $this->replayErrors($cache['errors'], $cache['warnings']); |
|
106 | - $this->configCache['cache'] = true; |
|
107 | - } else { |
|
108 | - $this->errorCount = $cache['errorCount']; |
|
109 | - $this->warningCount = $cache['warningCount']; |
|
110 | - $this->fixableCount = $cache['fixableCount']; |
|
111 | - } |
|
112 | - |
|
113 | - if (PHP_CODESNIFFER_VERBOSITY > 0 |
|
114 | - || (PHP_CODESNIFFER_CBF === true && empty($this->config->files) === false) |
|
115 | - ) { |
|
116 | - echo "[loaded from cache]... "; |
|
117 | - } |
|
118 | - |
|
119 | - $this->numTokens = $cache['numTokens']; |
|
120 | - $this->fromCache = true; |
|
121 | - return; |
|
122 | - }//end if |
|
123 | - |
|
124 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
125 | - echo PHP_EOL; |
|
126 | - } |
|
127 | - |
|
128 | - parent::process(); |
|
129 | - |
|
130 | - $cache = [ |
|
131 | - 'hash' => $hash, |
|
132 | - 'errors' => $this->errors, |
|
133 | - 'warnings' => $this->warnings, |
|
134 | - 'metrics' => $this->metrics, |
|
135 | - 'errorCount' => $this->errorCount, |
|
136 | - 'warningCount' => $this->warningCount, |
|
137 | - 'fixableCount' => $this->fixableCount, |
|
138 | - 'numTokens' => $this->numTokens, |
|
139 | - ]; |
|
140 | - |
|
141 | - Cache::set($this->path, $cache); |
|
142 | - |
|
143 | - // During caching, we don't filter out errors in any way, so |
|
144 | - // we need to do that manually now by replaying them. |
|
145 | - if ($this->configCache['recordErrors'] === true) { |
|
146 | - $this->configCache['cache'] = false; |
|
147 | - $this->replayErrors($this->errors, $this->warnings); |
|
148 | - $this->configCache['cache'] = true; |
|
149 | - } |
|
150 | - |
|
151 | - }//end process() |
|
152 | - |
|
153 | - |
|
154 | - /** |
|
155 | - * Clears and replays error and warnings for the file. |
|
156 | - * |
|
157 | - * Replaying errors and warnings allows for filtering rules to be changed |
|
158 | - * and then errors and warnings to be reapplied with the new rules. This is |
|
159 | - * particularly useful while caching. |
|
160 | - * |
|
161 | - * @param array $errors The list of errors to replay. |
|
162 | - * @param array $warnings The list of warnings to replay. |
|
163 | - * |
|
164 | - * @return void |
|
165 | - */ |
|
166 | - private function replayErrors($errors, $warnings) |
|
167 | - { |
|
168 | - $this->errors = []; |
|
169 | - $this->warnings = []; |
|
170 | - $this->errorCount = 0; |
|
171 | - $this->warningCount = 0; |
|
172 | - $this->fixableCount = 0; |
|
173 | - |
|
174 | - foreach ($errors as $line => $lineErrors) { |
|
175 | - foreach ($lineErrors as $column => $colErrors) { |
|
176 | - foreach ($colErrors as $error) { |
|
177 | - $this->activeListener = $error['listener']; |
|
178 | - $this->addMessage( |
|
179 | - true, |
|
180 | - $error['message'], |
|
181 | - $line, |
|
182 | - $column, |
|
183 | - $error['source'], |
|
184 | - [], |
|
185 | - $error['severity'], |
|
186 | - $error['fixable'] |
|
187 | - ); |
|
188 | - } |
|
189 | - } |
|
190 | - } |
|
191 | - |
|
192 | - foreach ($warnings as $line => $lineErrors) { |
|
193 | - foreach ($lineErrors as $column => $colErrors) { |
|
194 | - foreach ($colErrors as $error) { |
|
195 | - $this->activeListener = $error['listener']; |
|
196 | - $this->addMessage( |
|
197 | - false, |
|
198 | - $error['message'], |
|
199 | - $line, |
|
200 | - $column, |
|
201 | - $error['source'], |
|
202 | - [], |
|
203 | - $error['severity'], |
|
204 | - $error['fixable'] |
|
205 | - ); |
|
206 | - } |
|
207 | - } |
|
208 | - } |
|
209 | - |
|
210 | - }//end replayErrors() |
|
20 | + /** |
|
21 | + * Creates a LocalFile object and sets the content. |
|
22 | + * |
|
23 | + * @param string $path The absolute path to the file. |
|
24 | + * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
25 | + * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
26 | + * |
|
27 | + * @return void |
|
28 | + */ |
|
29 | + public function __construct($path, Ruleset $ruleset, Config $config) |
|
30 | + { |
|
31 | + $this->path = trim($path); |
|
32 | + if (is_readable($this->path) === false) { |
|
33 | + parent::__construct($this->path, $ruleset, $config); |
|
34 | + $error = 'Error opening file; file no longer exists or you do not have access to read the file'; |
|
35 | + $this->addMessage(true, $error, 1, 1, 'Internal.LocalFile', [], 5, false); |
|
36 | + $this->ignored = true; |
|
37 | + return; |
|
38 | + } |
|
39 | + |
|
40 | + // Before we go and spend time tokenizing this file, just check |
|
41 | + // to see if there is a tag up top to indicate that the whole |
|
42 | + // file should be ignored. It must be on one of the first two lines. |
|
43 | + if ($config->annotations === true) { |
|
44 | + $handle = fopen($this->path, 'r'); |
|
45 | + if ($handle !== false) { |
|
46 | + $firstContent = fgets($handle); |
|
47 | + $firstContent .= fgets($handle); |
|
48 | + fclose($handle); |
|
49 | + |
|
50 | + if (strpos($firstContent, '@codingStandardsIgnoreFile') !== false |
|
51 | + || stripos($firstContent, 'phpcs:ignorefile') !== false |
|
52 | + ) { |
|
53 | + // We are ignoring the whole file. |
|
54 | + $this->ignored = true; |
|
55 | + return; |
|
56 | + } |
|
57 | + } |
|
58 | + } |
|
59 | + |
|
60 | + $this->reloadContent(); |
|
61 | + |
|
62 | + parent::__construct($this->path, $ruleset, $config); |
|
63 | + |
|
64 | + }//end __construct() |
|
65 | + |
|
66 | + |
|
67 | + /** |
|
68 | + * Loads the latest version of the file's content from the file system. |
|
69 | + * |
|
70 | + * @return void |
|
71 | + */ |
|
72 | + public function reloadContent() |
|
73 | + { |
|
74 | + $this->setContent(file_get_contents($this->path)); |
|
75 | + |
|
76 | + }//end reloadContent() |
|
77 | + |
|
78 | + |
|
79 | + /** |
|
80 | + * Processes the file. |
|
81 | + * |
|
82 | + * @return void |
|
83 | + */ |
|
84 | + public function process() |
|
85 | + { |
|
86 | + if ($this->ignored === true) { |
|
87 | + return; |
|
88 | + } |
|
89 | + |
|
90 | + if ($this->configCache['cache'] === false) { |
|
91 | + parent::process(); |
|
92 | + return; |
|
93 | + } |
|
94 | + |
|
95 | + $hash = md5_file($this->path); |
|
96 | + $cache = Cache::get($this->path); |
|
97 | + if ($cache !== false && $cache['hash'] === $hash) { |
|
98 | + // We can't filter metrics, so just load all of them. |
|
99 | + $this->metrics = $cache['metrics']; |
|
100 | + |
|
101 | + if ($this->configCache['recordErrors'] === true) { |
|
102 | + // Replay the cached errors and warnings to filter out the ones |
|
103 | + // we don't need for this specific run. |
|
104 | + $this->configCache['cache'] = false; |
|
105 | + $this->replayErrors($cache['errors'], $cache['warnings']); |
|
106 | + $this->configCache['cache'] = true; |
|
107 | + } else { |
|
108 | + $this->errorCount = $cache['errorCount']; |
|
109 | + $this->warningCount = $cache['warningCount']; |
|
110 | + $this->fixableCount = $cache['fixableCount']; |
|
111 | + } |
|
112 | + |
|
113 | + if (PHP_CODESNIFFER_VERBOSITY > 0 |
|
114 | + || (PHP_CODESNIFFER_CBF === true && empty($this->config->files) === false) |
|
115 | + ) { |
|
116 | + echo "[loaded from cache]... "; |
|
117 | + } |
|
118 | + |
|
119 | + $this->numTokens = $cache['numTokens']; |
|
120 | + $this->fromCache = true; |
|
121 | + return; |
|
122 | + }//end if |
|
123 | + |
|
124 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
125 | + echo PHP_EOL; |
|
126 | + } |
|
127 | + |
|
128 | + parent::process(); |
|
129 | + |
|
130 | + $cache = [ |
|
131 | + 'hash' => $hash, |
|
132 | + 'errors' => $this->errors, |
|
133 | + 'warnings' => $this->warnings, |
|
134 | + 'metrics' => $this->metrics, |
|
135 | + 'errorCount' => $this->errorCount, |
|
136 | + 'warningCount' => $this->warningCount, |
|
137 | + 'fixableCount' => $this->fixableCount, |
|
138 | + 'numTokens' => $this->numTokens, |
|
139 | + ]; |
|
140 | + |
|
141 | + Cache::set($this->path, $cache); |
|
142 | + |
|
143 | + // During caching, we don't filter out errors in any way, so |
|
144 | + // we need to do that manually now by replaying them. |
|
145 | + if ($this->configCache['recordErrors'] === true) { |
|
146 | + $this->configCache['cache'] = false; |
|
147 | + $this->replayErrors($this->errors, $this->warnings); |
|
148 | + $this->configCache['cache'] = true; |
|
149 | + } |
|
150 | + |
|
151 | + }//end process() |
|
152 | + |
|
153 | + |
|
154 | + /** |
|
155 | + * Clears and replays error and warnings for the file. |
|
156 | + * |
|
157 | + * Replaying errors and warnings allows for filtering rules to be changed |
|
158 | + * and then errors and warnings to be reapplied with the new rules. This is |
|
159 | + * particularly useful while caching. |
|
160 | + * |
|
161 | + * @param array $errors The list of errors to replay. |
|
162 | + * @param array $warnings The list of warnings to replay. |
|
163 | + * |
|
164 | + * @return void |
|
165 | + */ |
|
166 | + private function replayErrors($errors, $warnings) |
|
167 | + { |
|
168 | + $this->errors = []; |
|
169 | + $this->warnings = []; |
|
170 | + $this->errorCount = 0; |
|
171 | + $this->warningCount = 0; |
|
172 | + $this->fixableCount = 0; |
|
173 | + |
|
174 | + foreach ($errors as $line => $lineErrors) { |
|
175 | + foreach ($lineErrors as $column => $colErrors) { |
|
176 | + foreach ($colErrors as $error) { |
|
177 | + $this->activeListener = $error['listener']; |
|
178 | + $this->addMessage( |
|
179 | + true, |
|
180 | + $error['message'], |
|
181 | + $line, |
|
182 | + $column, |
|
183 | + $error['source'], |
|
184 | + [], |
|
185 | + $error['severity'], |
|
186 | + $error['fixable'] |
|
187 | + ); |
|
188 | + } |
|
189 | + } |
|
190 | + } |
|
191 | + |
|
192 | + foreach ($warnings as $line => $lineErrors) { |
|
193 | + foreach ($lineErrors as $column => $colErrors) { |
|
194 | + foreach ($colErrors as $error) { |
|
195 | + $this->activeListener = $error['listener']; |
|
196 | + $this->addMessage( |
|
197 | + false, |
|
198 | + $error['message'], |
|
199 | + $line, |
|
200 | + $column, |
|
201 | + $error['source'], |
|
202 | + [], |
|
203 | + $error['severity'], |
|
204 | + $error['fixable'] |
|
205 | + ); |
|
206 | + } |
|
207 | + } |
|
208 | + } |
|
209 | + |
|
210 | + }//end replayErrors() |
|
211 | 211 | |
212 | 212 | |
213 | 213 | }//end class |
@@ -21,62 +21,62 @@ |
||
21 | 21 | { |
22 | 22 | |
23 | 23 | |
24 | - /** |
|
25 | - * Creates a DummyFile object and sets the content. |
|
26 | - * |
|
27 | - * @param string $content The content of the file. |
|
28 | - * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
29 | - * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
30 | - * |
|
31 | - * @return void |
|
32 | - */ |
|
33 | - public function __construct($content, Ruleset $ruleset, Config $config) |
|
34 | - { |
|
35 | - $this->setContent($content); |
|
24 | + /** |
|
25 | + * Creates a DummyFile object and sets the content. |
|
26 | + * |
|
27 | + * @param string $content The content of the file. |
|
28 | + * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
29 | + * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
30 | + * |
|
31 | + * @return void |
|
32 | + */ |
|
33 | + public function __construct($content, Ruleset $ruleset, Config $config) |
|
34 | + { |
|
35 | + $this->setContent($content); |
|
36 | 36 | |
37 | - // See if a filename was defined in the content. |
|
38 | - // This is done by including: phpcs_input_file: [file path] |
|
39 | - // as the first line of content. |
|
40 | - $path = 'STDIN'; |
|
41 | - if ($content !== null) { |
|
42 | - if (substr($content, 0, 17) === 'phpcs_input_file:') { |
|
43 | - $eolPos = strpos($content, $this->eolChar); |
|
44 | - $filename = trim(substr($content, 17, ($eolPos - 17))); |
|
45 | - $content = substr($content, ($eolPos + strlen($this->eolChar))); |
|
46 | - $path = $filename; |
|
37 | + // See if a filename was defined in the content. |
|
38 | + // This is done by including: phpcs_input_file: [file path] |
|
39 | + // as the first line of content. |
|
40 | + $path = 'STDIN'; |
|
41 | + if ($content !== null) { |
|
42 | + if (substr($content, 0, 17) === 'phpcs_input_file:') { |
|
43 | + $eolPos = strpos($content, $this->eolChar); |
|
44 | + $filename = trim(substr($content, 17, ($eolPos - 17))); |
|
45 | + $content = substr($content, ($eolPos + strlen($this->eolChar))); |
|
46 | + $path = $filename; |
|
47 | 47 | |
48 | - $this->setContent($content); |
|
49 | - } |
|
50 | - } |
|
48 | + $this->setContent($content); |
|
49 | + } |
|
50 | + } |
|
51 | 51 | |
52 | - // The CLI arg overrides anything passed in the content. |
|
53 | - if ($config->stdinPath !== null) { |
|
54 | - $path = $config->stdinPath; |
|
55 | - } |
|
52 | + // The CLI arg overrides anything passed in the content. |
|
53 | + if ($config->stdinPath !== null) { |
|
54 | + $path = $config->stdinPath; |
|
55 | + } |
|
56 | 56 | |
57 | - parent::__construct($path, $ruleset, $config); |
|
57 | + parent::__construct($path, $ruleset, $config); |
|
58 | 58 | |
59 | - }//end __construct() |
|
59 | + }//end __construct() |
|
60 | 60 | |
61 | 61 | |
62 | - /** |
|
63 | - * Set the error, warning, and fixable counts for the file. |
|
64 | - * |
|
65 | - * @param int $errorCount The number of errors found. |
|
66 | - * @param int $warningCount The number of warnings found. |
|
67 | - * @param int $fixableCount The number of fixable errors found. |
|
68 | - * @param int $fixedCount The number of errors that were fixed. |
|
69 | - * |
|
70 | - * @return void |
|
71 | - */ |
|
72 | - public function setErrorCounts($errorCount, $warningCount, $fixableCount, $fixedCount) |
|
73 | - { |
|
74 | - $this->errorCount = $errorCount; |
|
75 | - $this->warningCount = $warningCount; |
|
76 | - $this->fixableCount = $fixableCount; |
|
77 | - $this->fixedCount = $fixedCount; |
|
62 | + /** |
|
63 | + * Set the error, warning, and fixable counts for the file. |
|
64 | + * |
|
65 | + * @param int $errorCount The number of errors found. |
|
66 | + * @param int $warningCount The number of warnings found. |
|
67 | + * @param int $fixableCount The number of fixable errors found. |
|
68 | + * @param int $fixedCount The number of errors that were fixed. |
|
69 | + * |
|
70 | + * @return void |
|
71 | + */ |
|
72 | + public function setErrorCounts($errorCount, $warningCount, $fixableCount, $fixedCount) |
|
73 | + { |
|
74 | + $this->errorCount = $errorCount; |
|
75 | + $this->warningCount = $warningCount; |
|
76 | + $this->fixableCount = $fixableCount; |
|
77 | + $this->fixedCount = $fixedCount; |
|
78 | 78 | |
79 | - }//end setErrorCounts() |
|
79 | + }//end setErrorCounts() |
|
80 | 80 | |
81 | 81 | |
82 | 82 | }//end class |
@@ -17,1354 +17,1354 @@ |
||
17 | 17 | class Ruleset |
18 | 18 | { |
19 | 19 | |
20 | - /** |
|
21 | - * The name of the coding standard being used. |
|
22 | - * |
|
23 | - * If a top-level standard includes other standards, or sniffs |
|
24 | - * from other standards, only the name of the top-level standard |
|
25 | - * will be stored in here. |
|
26 | - * |
|
27 | - * If multiple top-level standards are being loaded into |
|
28 | - * a single ruleset object, this will store a comma separated list |
|
29 | - * of the top-level standard names. |
|
30 | - * |
|
31 | - * @var string |
|
32 | - */ |
|
33 | - public $name = ''; |
|
34 | - |
|
35 | - /** |
|
36 | - * A list of file paths for the ruleset files being used. |
|
37 | - * |
|
38 | - * @var string[] |
|
39 | - */ |
|
40 | - public $paths = []; |
|
41 | - |
|
42 | - /** |
|
43 | - * A list of regular expressions used to ignore specific sniffs for files and folders. |
|
44 | - * |
|
45 | - * Is also used to set global exclude patterns. |
|
46 | - * The key is the regular expression and the value is the type |
|
47 | - * of ignore pattern (absolute or relative). |
|
48 | - * |
|
49 | - * @var array<string, string> |
|
50 | - */ |
|
51 | - public $ignorePatterns = []; |
|
52 | - |
|
53 | - /** |
|
54 | - * A list of regular expressions used to include specific sniffs for files and folders. |
|
55 | - * |
|
56 | - * The key is the sniff code and the value is an array with |
|
57 | - * the key being a regular expression and the value is the type |
|
58 | - * of ignore pattern (absolute or relative). |
|
59 | - * |
|
60 | - * @var array<string, array<string, string>> |
|
61 | - */ |
|
62 | - public $includePatterns = []; |
|
63 | - |
|
64 | - /** |
|
65 | - * An array of sniff objects that are being used to check files. |
|
66 | - * |
|
67 | - * The key is the fully qualified name of the sniff class |
|
68 | - * and the value is the sniff object. |
|
69 | - * |
|
70 | - * @var array<string, \PHP_CodeSniffer\Sniffs\Sniff> |
|
71 | - */ |
|
72 | - public $sniffs = []; |
|
73 | - |
|
74 | - /** |
|
75 | - * A mapping of sniff codes to fully qualified class names. |
|
76 | - * |
|
77 | - * The key is the sniff code and the value |
|
78 | - * is the fully qualified name of the sniff class. |
|
79 | - * |
|
80 | - * @var array<string, string> |
|
81 | - */ |
|
82 | - public $sniffCodes = []; |
|
83 | - |
|
84 | - /** |
|
85 | - * An array of token types and the sniffs that are listening for them. |
|
86 | - * |
|
87 | - * The key is the token name being listened for and the value |
|
88 | - * is the sniff object. |
|
89 | - * |
|
90 | - * @var array<int, \PHP_CodeSniffer\Sniffs\Sniff> |
|
91 | - */ |
|
92 | - public $tokenListeners = []; |
|
93 | - |
|
94 | - /** |
|
95 | - * An array of rules from the ruleset.xml file. |
|
96 | - * |
|
97 | - * It may be empty, indicating that the ruleset does not override |
|
98 | - * any of the default sniff settings. |
|
99 | - * |
|
100 | - * @var array<string, mixed> |
|
101 | - */ |
|
102 | - public $ruleset = []; |
|
103 | - |
|
104 | - /** |
|
105 | - * The directories that the processed rulesets are in. |
|
106 | - * |
|
107 | - * @var string[] |
|
108 | - */ |
|
109 | - protected $rulesetDirs = []; |
|
110 | - |
|
111 | - /** |
|
112 | - * The config data for the run. |
|
113 | - * |
|
114 | - * @var \PHP_CodeSniffer\Config |
|
115 | - */ |
|
116 | - private $config = null; |
|
117 | - |
|
118 | - |
|
119 | - /** |
|
120 | - * Initialise the ruleset that the run will use. |
|
121 | - * |
|
122 | - * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
123 | - * |
|
124 | - * @return void |
|
125 | - */ |
|
126 | - public function __construct(Config $config) |
|
127 | - { |
|
128 | - // Ignore sniff restrictions if caching is on. |
|
129 | - $restrictions = []; |
|
130 | - $exclusions = []; |
|
131 | - if ($config->cache === false) { |
|
132 | - $restrictions = $config->sniffs; |
|
133 | - $exclusions = $config->exclude; |
|
134 | - } |
|
135 | - |
|
136 | - $this->config = $config; |
|
137 | - $sniffs = []; |
|
138 | - |
|
139 | - $standardPaths = []; |
|
140 | - foreach ($config->standards as $standard) { |
|
141 | - $installed = Util\Standards::getInstalledStandardPath($standard); |
|
142 | - if ($installed === null) { |
|
143 | - $standard = Util\Common::realpath($standard); |
|
144 | - if (is_dir($standard) === true |
|
145 | - && is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true |
|
146 | - ) { |
|
147 | - $standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); |
|
148 | - } |
|
149 | - } else { |
|
150 | - $standard = $installed; |
|
151 | - } |
|
152 | - |
|
153 | - $standardPaths[] = $standard; |
|
154 | - } |
|
155 | - |
|
156 | - foreach ($standardPaths as $standard) { |
|
157 | - $ruleset = @simplexml_load_string(file_get_contents($standard)); |
|
158 | - if ($ruleset !== false) { |
|
159 | - $standardName = (string) $ruleset['name']; |
|
160 | - if ($this->name !== '') { |
|
161 | - $this->name .= ', '; |
|
162 | - } |
|
163 | - |
|
164 | - $this->name .= $standardName; |
|
165 | - |
|
166 | - // Allow autoloading of custom files inside this standard. |
|
167 | - if (isset($ruleset['namespace']) === true) { |
|
168 | - $namespace = (string) $ruleset['namespace']; |
|
169 | - } else { |
|
170 | - $namespace = basename(dirname($standard)); |
|
171 | - } |
|
172 | - |
|
173 | - Autoload::addSearchPath(dirname($standard), $namespace); |
|
174 | - } |
|
175 | - |
|
176 | - if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) { |
|
177 | - // In unit tests, only register the sniffs that the test wants and not the entire standard. |
|
178 | - try { |
|
179 | - foreach ($restrictions as $restriction) { |
|
180 | - $sniffs = array_merge($sniffs, $this->expandRulesetReference($restriction, dirname($standard))); |
|
181 | - } |
|
182 | - } catch (RuntimeException $e) { |
|
183 | - // Sniff reference could not be expanded, which probably means this |
|
184 | - // is an installed standard. Let the unit test system take care of |
|
185 | - // setting the correct sniff for testing. |
|
186 | - return; |
|
187 | - } |
|
188 | - |
|
189 | - break; |
|
190 | - } |
|
191 | - |
|
192 | - if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
193 | - echo "Registering sniffs in the $standardName standard... "; |
|
194 | - if (count($config->standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) { |
|
195 | - echo PHP_EOL; |
|
196 | - } |
|
197 | - } |
|
198 | - |
|
199 | - $sniffs = array_merge($sniffs, $this->processRuleset($standard)); |
|
200 | - }//end foreach |
|
201 | - |
|
202 | - $sniffRestrictions = []; |
|
203 | - foreach ($restrictions as $sniffCode) { |
|
204 | - $parts = explode('.', strtolower($sniffCode)); |
|
205 | - $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; |
|
206 | - $sniffRestrictions[$sniffName] = true; |
|
207 | - } |
|
208 | - |
|
209 | - $sniffExclusions = []; |
|
210 | - foreach ($exclusions as $sniffCode) { |
|
211 | - $parts = explode('.', strtolower($sniffCode)); |
|
212 | - $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; |
|
213 | - $sniffExclusions[$sniffName] = true; |
|
214 | - } |
|
215 | - |
|
216 | - $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions); |
|
217 | - $this->populateTokenListeners(); |
|
218 | - |
|
219 | - $numSniffs = count($this->sniffs); |
|
220 | - if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
221 | - echo "DONE ($numSniffs sniffs registered)".PHP_EOL; |
|
222 | - } |
|
223 | - |
|
224 | - if ($numSniffs === 0) { |
|
225 | - throw new RuntimeException('No sniffs were registered'); |
|
226 | - } |
|
227 | - |
|
228 | - }//end __construct() |
|
229 | - |
|
230 | - |
|
231 | - /** |
|
232 | - * Prints a report showing the sniffs contained in a standard. |
|
233 | - * |
|
234 | - * @return void |
|
235 | - */ |
|
236 | - public function explain() |
|
237 | - { |
|
238 | - $sniffs = array_keys($this->sniffCodes); |
|
239 | - sort($sniffs); |
|
240 | - |
|
241 | - ob_start(); |
|
242 | - |
|
243 | - $lastStandard = null; |
|
244 | - $lastCount = ''; |
|
245 | - $sniffCount = count($sniffs); |
|
246 | - |
|
247 | - // Add a dummy entry to the end so we loop |
|
248 | - // one last time and clear the output buffer. |
|
249 | - $sniffs[] = ''; |
|
250 | - |
|
251 | - echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL; |
|
252 | - |
|
253 | - ob_start(); |
|
254 | - |
|
255 | - foreach ($sniffs as $i => $sniff) { |
|
256 | - if ($i === $sniffCount) { |
|
257 | - $currentStandard = null; |
|
258 | - } else { |
|
259 | - $currentStandard = substr($sniff, 0, strpos($sniff, '.')); |
|
260 | - if ($lastStandard === null) { |
|
261 | - $lastStandard = $currentStandard; |
|
262 | - } |
|
263 | - } |
|
264 | - |
|
265 | - if ($currentStandard !== $lastStandard) { |
|
266 | - $sniffList = ob_get_contents(); |
|
267 | - ob_end_clean(); |
|
268 | - |
|
269 | - echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff'; |
|
270 | - if ($lastCount > 1) { |
|
271 | - echo 's'; |
|
272 | - } |
|
273 | - |
|
274 | - echo ')'.PHP_EOL; |
|
275 | - echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10)); |
|
276 | - echo PHP_EOL; |
|
277 | - echo $sniffList; |
|
278 | - |
|
279 | - $lastStandard = $currentStandard; |
|
280 | - $lastCount = 0; |
|
281 | - |
|
282 | - if ($currentStandard === null) { |
|
283 | - break; |
|
284 | - } |
|
285 | - |
|
286 | - ob_start(); |
|
287 | - }//end if |
|
288 | - |
|
289 | - echo ' '.$sniff.PHP_EOL; |
|
290 | - $lastCount++; |
|
291 | - }//end foreach |
|
292 | - |
|
293 | - }//end explain() |
|
294 | - |
|
295 | - |
|
296 | - /** |
|
297 | - * Processes a single ruleset and returns a list of the sniffs it represents. |
|
298 | - * |
|
299 | - * Rules founds within the ruleset are processed immediately, but sniff classes |
|
300 | - * are not registered by this method. |
|
301 | - * |
|
302 | - * @param string $rulesetPath The path to a ruleset XML file. |
|
303 | - * @param int $depth How many nested processing steps we are in. This |
|
304 | - * is only used for debug output. |
|
305 | - * |
|
306 | - * @return string[] |
|
307 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the ruleset path is invalid. |
|
308 | - */ |
|
309 | - public function processRuleset($rulesetPath, $depth=0) |
|
310 | - { |
|
311 | - $rulesetPath = Util\Common::realpath($rulesetPath); |
|
312 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
313 | - echo str_repeat("\t", $depth); |
|
314 | - echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath, $this->config->basepath).PHP_EOL; |
|
315 | - } |
|
316 | - |
|
317 | - libxml_use_internal_errors(true); |
|
318 | - $ruleset = simplexml_load_string(file_get_contents($rulesetPath)); |
|
319 | - if ($ruleset === false) { |
|
320 | - $errorMsg = "Ruleset $rulesetPath is not valid".PHP_EOL; |
|
321 | - $errors = libxml_get_errors(); |
|
322 | - foreach ($errors as $error) { |
|
323 | - $errorMsg .= '- On line '.$error->line.', column '.$error->column.': '.$error->message; |
|
324 | - } |
|
325 | - |
|
326 | - libxml_clear_errors(); |
|
327 | - throw new RuntimeException($errorMsg); |
|
328 | - } |
|
329 | - |
|
330 | - libxml_use_internal_errors(false); |
|
331 | - |
|
332 | - $ownSniffs = []; |
|
333 | - $includedSniffs = []; |
|
334 | - $excludedSniffs = []; |
|
335 | - |
|
336 | - $this->paths[] = $rulesetPath; |
|
337 | - $rulesetDir = dirname($rulesetPath); |
|
338 | - $this->rulesetDirs[] = $rulesetDir; |
|
339 | - |
|
340 | - $sniffDir = $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs'; |
|
341 | - if (is_dir($sniffDir) === true) { |
|
342 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
343 | - echo str_repeat("\t", $depth); |
|
344 | - echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir, $this->config->basepath).' directory'.PHP_EOL; |
|
345 | - } |
|
346 | - |
|
347 | - $ownSniffs = $this->expandSniffDirectory($sniffDir, $depth); |
|
348 | - } |
|
349 | - |
|
350 | - // Included custom autoloaders. |
|
351 | - foreach ($ruleset->{'autoload'} as $autoload) { |
|
352 | - if ($this->shouldProcessElement($autoload) === false) { |
|
353 | - continue; |
|
354 | - } |
|
355 | - |
|
356 | - $autoloadPath = (string) $autoload; |
|
357 | - if (is_file($autoloadPath) === false) { |
|
358 | - $autoloadPath = Util\Common::realPath(dirname($rulesetPath).DIRECTORY_SEPARATOR.$autoloadPath); |
|
359 | - } |
|
360 | - |
|
361 | - if ($autoloadPath === false) { |
|
362 | - throw new RuntimeException('The specified autoload file "'.$autoload.'" does not exist'); |
|
363 | - } |
|
364 | - |
|
365 | - include_once $autoloadPath; |
|
366 | - |
|
367 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
368 | - echo str_repeat("\t", $depth); |
|
369 | - echo "\t=> included autoloader $autoloadPath".PHP_EOL; |
|
370 | - } |
|
371 | - }//end foreach |
|
372 | - |
|
373 | - // Process custom sniff config settings. |
|
374 | - foreach ($ruleset->{'config'} as $config) { |
|
375 | - if ($this->shouldProcessElement($config) === false) { |
|
376 | - continue; |
|
377 | - } |
|
378 | - |
|
379 | - Config::setConfigData((string) $config['name'], (string) $config['value'], true); |
|
380 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
381 | - echo str_repeat("\t", $depth); |
|
382 | - echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL; |
|
383 | - } |
|
384 | - } |
|
385 | - |
|
386 | - foreach ($ruleset->rule as $rule) { |
|
387 | - if (isset($rule['ref']) === false |
|
388 | - || $this->shouldProcessElement($rule) === false |
|
389 | - ) { |
|
390 | - continue; |
|
391 | - } |
|
392 | - |
|
393 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
394 | - echo str_repeat("\t", $depth); |
|
395 | - echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL; |
|
396 | - } |
|
397 | - |
|
398 | - $expandedSniffs = $this->expandRulesetReference((string) $rule['ref'], $rulesetDir, $depth); |
|
399 | - $newSniffs = array_diff($expandedSniffs, $includedSniffs); |
|
400 | - $includedSniffs = array_merge($includedSniffs, $expandedSniffs); |
|
401 | - |
|
402 | - $parts = explode('.', $rule['ref']); |
|
403 | - if (count($parts) === 4 |
|
404 | - && $parts[0] !== '' |
|
405 | - && $parts[1] !== '' |
|
406 | - && $parts[2] !== '' |
|
407 | - ) { |
|
408 | - $sniffCode = $parts[0].'.'.$parts[1].'.'.$parts[2]; |
|
409 | - if (isset($this->ruleset[$sniffCode]['severity']) === true |
|
410 | - && $this->ruleset[$sniffCode]['severity'] === 0 |
|
411 | - ) { |
|
412 | - // This sniff code has already been turned off, but now |
|
413 | - // it is being explicitly included again, so turn it back on. |
|
414 | - $this->ruleset[(string) $rule['ref']]['severity'] = 5; |
|
415 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
416 | - echo str_repeat("\t", $depth); |
|
417 | - echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL; |
|
418 | - echo str_repeat("\t", $depth); |
|
419 | - echo "\t\t=> severity set to 5".PHP_EOL; |
|
420 | - } |
|
421 | - } else if (empty($newSniffs) === false) { |
|
422 | - $newSniff = $newSniffs[0]; |
|
423 | - if (in_array($newSniff, $ownSniffs, true) === false) { |
|
424 | - // Including a sniff that hasn't been included higher up, but |
|
425 | - // only including a single message from it. So turn off all messages in |
|
426 | - // the sniff, except this one. |
|
427 | - $this->ruleset[$sniffCode]['severity'] = 0; |
|
428 | - $this->ruleset[(string) $rule['ref']]['severity'] = 5; |
|
429 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
430 | - echo str_repeat("\t", $depth); |
|
431 | - echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL; |
|
432 | - } |
|
433 | - } |
|
434 | - }//end if |
|
435 | - }//end if |
|
436 | - |
|
437 | - if (isset($rule->exclude) === true) { |
|
438 | - foreach ($rule->exclude as $exclude) { |
|
439 | - if (isset($exclude['name']) === false) { |
|
440 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
441 | - echo str_repeat("\t", $depth); |
|
442 | - echo "\t\t* ignoring empty exclude rule *".PHP_EOL; |
|
443 | - echo "\t\t\t=> ".$exclude->asXML().PHP_EOL; |
|
444 | - } |
|
445 | - |
|
446 | - continue; |
|
447 | - } |
|
448 | - |
|
449 | - if ($this->shouldProcessElement($exclude) === false) { |
|
450 | - continue; |
|
451 | - } |
|
452 | - |
|
453 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
454 | - echo str_repeat("\t", $depth); |
|
455 | - echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL; |
|
456 | - } |
|
457 | - |
|
458 | - // Check if a single code is being excluded, which is a shortcut |
|
459 | - // for setting the severity of the message to 0. |
|
460 | - $parts = explode('.', $exclude['name']); |
|
461 | - if (count($parts) === 4) { |
|
462 | - $this->ruleset[(string) $exclude['name']]['severity'] = 0; |
|
463 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
464 | - echo str_repeat("\t", $depth); |
|
465 | - echo "\t\t=> severity set to 0".PHP_EOL; |
|
466 | - } |
|
467 | - } else { |
|
468 | - $excludedSniffs = array_merge( |
|
469 | - $excludedSniffs, |
|
470 | - $this->expandRulesetReference((string) $exclude['name'], $rulesetDir, ($depth + 1)) |
|
471 | - ); |
|
472 | - } |
|
473 | - }//end foreach |
|
474 | - }//end if |
|
475 | - |
|
476 | - $this->processRule($rule, $newSniffs, $depth); |
|
477 | - }//end foreach |
|
478 | - |
|
479 | - // Process custom command line arguments. |
|
480 | - $cliArgs = []; |
|
481 | - foreach ($ruleset->{'arg'} as $arg) { |
|
482 | - if ($this->shouldProcessElement($arg) === false) { |
|
483 | - continue; |
|
484 | - } |
|
485 | - |
|
486 | - if (isset($arg['name']) === true) { |
|
487 | - $argString = '--'.(string) $arg['name']; |
|
488 | - if (isset($arg['value']) === true) { |
|
489 | - $argString .= '='.(string) $arg['value']; |
|
490 | - } |
|
491 | - } else { |
|
492 | - $argString = '-'.(string) $arg['value']; |
|
493 | - } |
|
494 | - |
|
495 | - $cliArgs[] = $argString; |
|
496 | - |
|
497 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
498 | - echo str_repeat("\t", $depth); |
|
499 | - echo "\t=> set command line value $argString".PHP_EOL; |
|
500 | - } |
|
501 | - }//end foreach |
|
502 | - |
|
503 | - // Set custom php ini values as CLI args. |
|
504 | - foreach ($ruleset->{'ini'} as $arg) { |
|
505 | - if ($this->shouldProcessElement($arg) === false) { |
|
506 | - continue; |
|
507 | - } |
|
508 | - |
|
509 | - if (isset($arg['name']) === false) { |
|
510 | - continue; |
|
511 | - } |
|
512 | - |
|
513 | - $name = (string) $arg['name']; |
|
514 | - $argString = $name; |
|
515 | - if (isset($arg['value']) === true) { |
|
516 | - $value = (string) $arg['value']; |
|
517 | - $argString .= "=$value"; |
|
518 | - } else { |
|
519 | - $value = 'true'; |
|
520 | - } |
|
521 | - |
|
522 | - $cliArgs[] = '-d'; |
|
523 | - $cliArgs[] = $argString; |
|
524 | - |
|
525 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
526 | - echo str_repeat("\t", $depth); |
|
527 | - echo "\t=> set PHP ini value $name to $value".PHP_EOL; |
|
528 | - } |
|
529 | - }//end foreach |
|
530 | - |
|
531 | - if (empty($this->config->files) === true) { |
|
532 | - // Process hard-coded file paths. |
|
533 | - foreach ($ruleset->{'file'} as $file) { |
|
534 | - $file = (string) $file; |
|
535 | - $cliArgs[] = $file; |
|
536 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
537 | - echo str_repeat("\t", $depth); |
|
538 | - echo "\t=> added \"$file\" to the file list".PHP_EOL; |
|
539 | - } |
|
540 | - } |
|
541 | - } |
|
542 | - |
|
543 | - if (empty($cliArgs) === false) { |
|
544 | - // Change the directory so all relative paths are worked |
|
545 | - // out based on the location of the ruleset instead of |
|
546 | - // the location of the user. |
|
547 | - $inPhar = Util\Common::isPharFile($rulesetDir); |
|
548 | - if ($inPhar === false) { |
|
549 | - $currentDir = getcwd(); |
|
550 | - chdir($rulesetDir); |
|
551 | - } |
|
552 | - |
|
553 | - $this->config->setCommandLineValues($cliArgs); |
|
554 | - |
|
555 | - if ($inPhar === false) { |
|
556 | - chdir($currentDir); |
|
557 | - } |
|
558 | - } |
|
559 | - |
|
560 | - // Process custom ignore pattern rules. |
|
561 | - foreach ($ruleset->{'exclude-pattern'} as $pattern) { |
|
562 | - if ($this->shouldProcessElement($pattern) === false) { |
|
563 | - continue; |
|
564 | - } |
|
565 | - |
|
566 | - if (isset($pattern['type']) === false) { |
|
567 | - $pattern['type'] = 'absolute'; |
|
568 | - } |
|
569 | - |
|
570 | - $this->ignorePatterns[(string) $pattern] = (string) $pattern['type']; |
|
571 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
572 | - echo str_repeat("\t", $depth); |
|
573 | - echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; |
|
574 | - } |
|
575 | - } |
|
576 | - |
|
577 | - $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs)); |
|
578 | - $excludedSniffs = array_unique($excludedSniffs); |
|
579 | - |
|
580 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
581 | - $included = count($includedSniffs); |
|
582 | - $excluded = count($excludedSniffs); |
|
583 | - echo str_repeat("\t", $depth); |
|
584 | - echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL; |
|
585 | - } |
|
586 | - |
|
587 | - // Merge our own sniff list with our externally included |
|
588 | - // sniff list, but filter out any excluded sniffs. |
|
589 | - $files = []; |
|
590 | - foreach ($includedSniffs as $sniff) { |
|
591 | - if (in_array($sniff, $excludedSniffs, true) === true) { |
|
592 | - continue; |
|
593 | - } else { |
|
594 | - $files[] = Util\Common::realpath($sniff); |
|
595 | - } |
|
596 | - } |
|
597 | - |
|
598 | - return $files; |
|
599 | - |
|
600 | - }//end processRuleset() |
|
601 | - |
|
602 | - |
|
603 | - /** |
|
604 | - * Expands a directory into a list of sniff files within. |
|
605 | - * |
|
606 | - * @param string $directory The path to a directory. |
|
607 | - * @param int $depth How many nested processing steps we are in. This |
|
608 | - * is only used for debug output. |
|
609 | - * |
|
610 | - * @return array |
|
611 | - */ |
|
612 | - private function expandSniffDirectory($directory, $depth=0) |
|
613 | - { |
|
614 | - $sniffs = []; |
|
615 | - |
|
616 | - $rdi = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); |
|
617 | - $di = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD); |
|
618 | - |
|
619 | - $dirLen = strlen($directory); |
|
620 | - |
|
621 | - foreach ($di as $file) { |
|
622 | - $filename = $file->getFilename(); |
|
623 | - |
|
624 | - // Skip hidden files. |
|
625 | - if (substr($filename, 0, 1) === '.') { |
|
626 | - continue; |
|
627 | - } |
|
628 | - |
|
629 | - // We are only interested in PHP and sniff files. |
|
630 | - $fileParts = explode('.', $filename); |
|
631 | - if (array_pop($fileParts) !== 'php') { |
|
632 | - continue; |
|
633 | - } |
|
634 | - |
|
635 | - $basename = basename($filename, '.php'); |
|
636 | - if (substr($basename, -5) !== 'Sniff') { |
|
637 | - continue; |
|
638 | - } |
|
639 | - |
|
640 | - $path = $file->getPathname(); |
|
641 | - |
|
642 | - // Skip files in hidden directories within the Sniffs directory of this |
|
643 | - // standard. We use the offset with strpos() to allow hidden directories |
|
644 | - // before, valid example: |
|
645 | - // /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/... |
|
646 | - if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) { |
|
647 | - continue; |
|
648 | - } |
|
649 | - |
|
650 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
651 | - echo str_repeat("\t", $depth); |
|
652 | - echo "\t\t=> ".Util\Common::stripBasepath($path, $this->config->basepath).PHP_EOL; |
|
653 | - } |
|
654 | - |
|
655 | - $sniffs[] = $path; |
|
656 | - }//end foreach |
|
657 | - |
|
658 | - return $sniffs; |
|
659 | - |
|
660 | - }//end expandSniffDirectory() |
|
661 | - |
|
662 | - |
|
663 | - /** |
|
664 | - * Expands a ruleset reference into a list of sniff files. |
|
665 | - * |
|
666 | - * @param string $ref The reference from the ruleset XML file. |
|
667 | - * @param string $rulesetDir The directory of the ruleset XML file, used to |
|
668 | - * evaluate relative paths. |
|
669 | - * @param int $depth How many nested processing steps we are in. This |
|
670 | - * is only used for debug output. |
|
671 | - * |
|
672 | - * @return array |
|
673 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the reference is invalid. |
|
674 | - */ |
|
675 | - private function expandRulesetReference($ref, $rulesetDir, $depth=0) |
|
676 | - { |
|
677 | - // Ignore internal sniffs codes as they are used to only |
|
678 | - // hide and change internal messages. |
|
679 | - if (substr($ref, 0, 9) === 'Internal.') { |
|
680 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
681 | - echo str_repeat("\t", $depth); |
|
682 | - echo "\t\t* ignoring internal sniff code *".PHP_EOL; |
|
683 | - } |
|
684 | - |
|
685 | - return []; |
|
686 | - } |
|
687 | - |
|
688 | - // As sniffs can't begin with a full stop, assume references in |
|
689 | - // this format are relative paths and attempt to convert them |
|
690 | - // to absolute paths. If this fails, let the reference run through |
|
691 | - // the normal checks and have it fail as normal. |
|
692 | - if (substr($ref, 0, 1) === '.') { |
|
693 | - $realpath = Util\Common::realpath($rulesetDir.'/'.$ref); |
|
694 | - if ($realpath !== false) { |
|
695 | - $ref = $realpath; |
|
696 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
697 | - echo str_repeat("\t", $depth); |
|
698 | - echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
699 | - } |
|
700 | - } |
|
701 | - } |
|
702 | - |
|
703 | - // As sniffs can't begin with a tilde, assume references in |
|
704 | - // this format are relative to the user's home directory. |
|
705 | - if (substr($ref, 0, 2) === '~/') { |
|
706 | - $realpath = Util\Common::realpath($ref); |
|
707 | - if ($realpath !== false) { |
|
708 | - $ref = $realpath; |
|
709 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
710 | - echo str_repeat("\t", $depth); |
|
711 | - echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
712 | - } |
|
713 | - } |
|
714 | - } |
|
715 | - |
|
716 | - if (is_file($ref) === true) { |
|
717 | - if (substr($ref, -9) === 'Sniff.php') { |
|
718 | - // A single external sniff. |
|
719 | - $this->rulesetDirs[] = dirname(dirname(dirname($ref))); |
|
720 | - return [$ref]; |
|
721 | - } |
|
722 | - } else { |
|
723 | - // See if this is a whole standard being referenced. |
|
724 | - $path = Util\Standards::getInstalledStandardPath($ref); |
|
725 | - if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { |
|
726 | - // If the ruleset exists inside the phar file, use it. |
|
727 | - if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { |
|
728 | - $path .= DIRECTORY_SEPARATOR.'ruleset.xml'; |
|
729 | - } else { |
|
730 | - $path = null; |
|
731 | - } |
|
732 | - } |
|
733 | - |
|
734 | - if ($path !== null) { |
|
735 | - $ref = $path; |
|
736 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
737 | - echo str_repeat("\t", $depth); |
|
738 | - echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
739 | - } |
|
740 | - } else if (is_dir($ref) === false) { |
|
741 | - // Work out the sniff path. |
|
742 | - $sepPos = strpos($ref, DIRECTORY_SEPARATOR); |
|
743 | - if ($sepPos !== false) { |
|
744 | - $stdName = substr($ref, 0, $sepPos); |
|
745 | - $path = substr($ref, $sepPos); |
|
746 | - } else { |
|
747 | - $parts = explode('.', $ref); |
|
748 | - $stdName = $parts[0]; |
|
749 | - if (count($parts) === 1) { |
|
750 | - // A whole standard? |
|
751 | - $path = ''; |
|
752 | - } else if (count($parts) === 2) { |
|
753 | - // A directory of sniffs? |
|
754 | - $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1]; |
|
755 | - } else { |
|
756 | - // A single sniff? |
|
757 | - $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php'; |
|
758 | - } |
|
759 | - } |
|
760 | - |
|
761 | - $newRef = false; |
|
762 | - $stdPath = Util\Standards::getInstalledStandardPath($stdName); |
|
763 | - if ($stdPath !== null && $path !== '') { |
|
764 | - if (Util\Common::isPharFile($stdPath) === true |
|
765 | - && strpos($stdPath, 'ruleset.xml') === false |
|
766 | - ) { |
|
767 | - // Phar files can only return the directory, |
|
768 | - // since ruleset can be omitted if building one standard. |
|
769 | - $newRef = Util\Common::realpath($stdPath.$path); |
|
770 | - } else { |
|
771 | - $newRef = Util\Common::realpath(dirname($stdPath).$path); |
|
772 | - } |
|
773 | - } |
|
774 | - |
|
775 | - if ($newRef === false) { |
|
776 | - // The sniff is not locally installed, so check if it is being |
|
777 | - // referenced as a remote sniff outside the install. We do this |
|
778 | - // by looking through all directories where we have found ruleset |
|
779 | - // files before, looking for ones for this particular standard, |
|
780 | - // and seeing if it is in there. |
|
781 | - foreach ($this->rulesetDirs as $dir) { |
|
782 | - if (strtolower(basename($dir)) !== strtolower($stdName)) { |
|
783 | - continue; |
|
784 | - } |
|
785 | - |
|
786 | - $newRef = Util\Common::realpath($dir.$path); |
|
787 | - |
|
788 | - if ($newRef !== false) { |
|
789 | - $ref = $newRef; |
|
790 | - } |
|
791 | - } |
|
792 | - } else { |
|
793 | - $ref = $newRef; |
|
794 | - } |
|
795 | - |
|
796 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
797 | - echo str_repeat("\t", $depth); |
|
798 | - echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
799 | - } |
|
800 | - }//end if |
|
801 | - }//end if |
|
802 | - |
|
803 | - if (is_dir($ref) === true) { |
|
804 | - if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { |
|
805 | - // We are referencing an external coding standard. |
|
806 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
807 | - echo str_repeat("\t", $depth); |
|
808 | - echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL; |
|
809 | - } |
|
810 | - |
|
811 | - return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2)); |
|
812 | - } else { |
|
813 | - // We are referencing a whole directory of sniffs. |
|
814 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
815 | - echo str_repeat("\t", $depth); |
|
816 | - echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL; |
|
817 | - echo str_repeat("\t", $depth); |
|
818 | - echo "\t\tAdding sniff files from directory".PHP_EOL; |
|
819 | - } |
|
820 | - |
|
821 | - return $this->expandSniffDirectory($ref, ($depth + 1)); |
|
822 | - } |
|
823 | - } else { |
|
824 | - if (is_file($ref) === false) { |
|
825 | - $error = "Referenced sniff \"$ref\" does not exist"; |
|
826 | - throw new RuntimeException($error); |
|
827 | - } |
|
828 | - |
|
829 | - if (substr($ref, -9) === 'Sniff.php') { |
|
830 | - // A single sniff. |
|
831 | - return [$ref]; |
|
832 | - } else { |
|
833 | - // Assume an external ruleset.xml file. |
|
834 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
835 | - echo str_repeat("\t", $depth); |
|
836 | - echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL; |
|
837 | - } |
|
838 | - |
|
839 | - return $this->processRuleset($ref, ($depth + 2)); |
|
840 | - } |
|
841 | - }//end if |
|
842 | - |
|
843 | - }//end expandRulesetReference() |
|
844 | - |
|
845 | - |
|
846 | - /** |
|
847 | - * Processes a rule from a ruleset XML file, overriding built-in defaults. |
|
848 | - * |
|
849 | - * @param \SimpleXMLElement $rule The rule object from a ruleset XML file. |
|
850 | - * @param string[] $newSniffs An array of sniffs that got included by this rule. |
|
851 | - * @param int $depth How many nested processing steps we are in. |
|
852 | - * This is only used for debug output. |
|
853 | - * |
|
854 | - * @return void |
|
855 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If rule settings are invalid. |
|
856 | - */ |
|
857 | - private function processRule($rule, $newSniffs, $depth=0) |
|
858 | - { |
|
859 | - $ref = (string) $rule['ref']; |
|
860 | - $todo = [$ref]; |
|
861 | - |
|
862 | - $parts = explode('.', $ref); |
|
863 | - if (count($parts) <= 2) { |
|
864 | - // We are processing a standard or a category of sniffs. |
|
865 | - foreach ($newSniffs as $sniffFile) { |
|
866 | - $parts = explode(DIRECTORY_SEPARATOR, $sniffFile); |
|
867 | - $sniffName = array_pop($parts); |
|
868 | - $sniffCategory = array_pop($parts); |
|
869 | - array_pop($parts); |
|
870 | - $sniffStandard = array_pop($parts); |
|
871 | - $todo[] = $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName, 0, -9); |
|
872 | - } |
|
873 | - } |
|
874 | - |
|
875 | - foreach ($todo as $code) { |
|
876 | - // Custom severity. |
|
877 | - if (isset($rule->severity) === true |
|
878 | - && $this->shouldProcessElement($rule->severity) === true |
|
879 | - ) { |
|
880 | - if (isset($this->ruleset[$code]) === false) { |
|
881 | - $this->ruleset[$code] = []; |
|
882 | - } |
|
883 | - |
|
884 | - $this->ruleset[$code]['severity'] = (int) $rule->severity; |
|
885 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
886 | - echo str_repeat("\t", $depth); |
|
887 | - echo "\t\t=> severity set to ".(int) $rule->severity; |
|
888 | - if ($code !== $ref) { |
|
889 | - echo " for $code"; |
|
890 | - } |
|
891 | - |
|
892 | - echo PHP_EOL; |
|
893 | - } |
|
894 | - } |
|
895 | - |
|
896 | - // Custom message type. |
|
897 | - if (isset($rule->type) === true |
|
898 | - && $this->shouldProcessElement($rule->type) === true |
|
899 | - ) { |
|
900 | - if (isset($this->ruleset[$code]) === false) { |
|
901 | - $this->ruleset[$code] = []; |
|
902 | - } |
|
903 | - |
|
904 | - $type = strtolower((string) $rule->type); |
|
905 | - if ($type !== 'error' && $type !== 'warning') { |
|
906 | - throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\""); |
|
907 | - } |
|
908 | - |
|
909 | - $this->ruleset[$code]['type'] = $type; |
|
910 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
911 | - echo str_repeat("\t", $depth); |
|
912 | - echo "\t\t=> message type set to ".(string) $rule->type; |
|
913 | - if ($code !== $ref) { |
|
914 | - echo " for $code"; |
|
915 | - } |
|
916 | - |
|
917 | - echo PHP_EOL; |
|
918 | - } |
|
919 | - }//end if |
|
920 | - |
|
921 | - // Custom message. |
|
922 | - if (isset($rule->message) === true |
|
923 | - && $this->shouldProcessElement($rule->message) === true |
|
924 | - ) { |
|
925 | - if (isset($this->ruleset[$code]) === false) { |
|
926 | - $this->ruleset[$code] = []; |
|
927 | - } |
|
928 | - |
|
929 | - $this->ruleset[$code]['message'] = (string) $rule->message; |
|
930 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
931 | - echo str_repeat("\t", $depth); |
|
932 | - echo "\t\t=> message set to ".(string) $rule->message; |
|
933 | - if ($code !== $ref) { |
|
934 | - echo " for $code"; |
|
935 | - } |
|
936 | - |
|
937 | - echo PHP_EOL; |
|
938 | - } |
|
939 | - } |
|
940 | - |
|
941 | - // Custom properties. |
|
942 | - if (isset($rule->properties) === true |
|
943 | - && $this->shouldProcessElement($rule->properties) === true |
|
944 | - ) { |
|
945 | - foreach ($rule->properties->property as $prop) { |
|
946 | - if ($this->shouldProcessElement($prop) === false) { |
|
947 | - continue; |
|
948 | - } |
|
949 | - |
|
950 | - if (isset($this->ruleset[$code]) === false) { |
|
951 | - $this->ruleset[$code] = [ |
|
952 | - 'properties' => [], |
|
953 | - ]; |
|
954 | - } else if (isset($this->ruleset[$code]['properties']) === false) { |
|
955 | - $this->ruleset[$code]['properties'] = []; |
|
956 | - } |
|
957 | - |
|
958 | - $name = (string) $prop['name']; |
|
959 | - if (isset($prop['type']) === true |
|
960 | - && (string) $prop['type'] === 'array' |
|
961 | - ) { |
|
962 | - $values = []; |
|
963 | - if (isset($prop['extend']) === true |
|
964 | - && (string) $prop['extend'] === 'true' |
|
965 | - && isset($this->ruleset[$code]['properties'][$name]) === true |
|
966 | - ) { |
|
967 | - $values = $this->ruleset[$code]['properties'][$name]; |
|
968 | - } |
|
969 | - |
|
970 | - if (isset($prop->element) === true) { |
|
971 | - $printValue = ''; |
|
972 | - foreach ($prop->element as $element) { |
|
973 | - if ($this->shouldProcessElement($element) === false) { |
|
974 | - continue; |
|
975 | - } |
|
976 | - |
|
977 | - $value = (string) $element['value']; |
|
978 | - if (isset($element['key']) === true) { |
|
979 | - $key = (string) $element['key']; |
|
980 | - $values[$key] = $value; |
|
981 | - $printValue .= $key.'=>'.$value.','; |
|
982 | - } else { |
|
983 | - $values[] = $value; |
|
984 | - $printValue .= $value.','; |
|
985 | - } |
|
986 | - } |
|
987 | - |
|
988 | - $printValue = rtrim($printValue, ','); |
|
989 | - } else { |
|
990 | - $value = (string) $prop['value']; |
|
991 | - $printValue = $value; |
|
992 | - foreach (explode(',', $value) as $val) { |
|
993 | - list($k, $v) = explode('=>', $val.'=>'); |
|
994 | - if ($v !== '') { |
|
995 | - $values[trim($k)] = trim($v); |
|
996 | - } else { |
|
997 | - $values[] = trim($k); |
|
998 | - } |
|
999 | - } |
|
1000 | - }//end if |
|
1001 | - |
|
1002 | - $this->ruleset[$code]['properties'][$name] = $values; |
|
1003 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1004 | - echo str_repeat("\t", $depth); |
|
1005 | - echo "\t\t=> array property \"$name\" set to \"$printValue\""; |
|
1006 | - if ($code !== $ref) { |
|
1007 | - echo " for $code"; |
|
1008 | - } |
|
1009 | - |
|
1010 | - echo PHP_EOL; |
|
1011 | - } |
|
1012 | - } else { |
|
1013 | - $this->ruleset[$code]['properties'][$name] = (string) $prop['value']; |
|
1014 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1015 | - echo str_repeat("\t", $depth); |
|
1016 | - echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'; |
|
1017 | - if ($code !== $ref) { |
|
1018 | - echo " for $code"; |
|
1019 | - } |
|
1020 | - |
|
1021 | - echo PHP_EOL; |
|
1022 | - } |
|
1023 | - }//end if |
|
1024 | - }//end foreach |
|
1025 | - }//end if |
|
1026 | - |
|
1027 | - // Ignore patterns. |
|
1028 | - foreach ($rule->{'exclude-pattern'} as $pattern) { |
|
1029 | - if ($this->shouldProcessElement($pattern) === false) { |
|
1030 | - continue; |
|
1031 | - } |
|
1032 | - |
|
1033 | - if (isset($this->ignorePatterns[$code]) === false) { |
|
1034 | - $this->ignorePatterns[$code] = []; |
|
1035 | - } |
|
1036 | - |
|
1037 | - if (isset($pattern['type']) === false) { |
|
1038 | - $pattern['type'] = 'absolute'; |
|
1039 | - } |
|
1040 | - |
|
1041 | - $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type']; |
|
1042 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1043 | - echo str_repeat("\t", $depth); |
|
1044 | - echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern'; |
|
1045 | - if ($code !== $ref) { |
|
1046 | - echo " for $code"; |
|
1047 | - } |
|
1048 | - |
|
1049 | - echo ': '.(string) $pattern.PHP_EOL; |
|
1050 | - } |
|
1051 | - }//end foreach |
|
1052 | - |
|
1053 | - // Include patterns. |
|
1054 | - foreach ($rule->{'include-pattern'} as $pattern) { |
|
1055 | - if ($this->shouldProcessElement($pattern) === false) { |
|
1056 | - continue; |
|
1057 | - } |
|
1058 | - |
|
1059 | - if (isset($this->includePatterns[$code]) === false) { |
|
1060 | - $this->includePatterns[$code] = []; |
|
1061 | - } |
|
1062 | - |
|
1063 | - if (isset($pattern['type']) === false) { |
|
1064 | - $pattern['type'] = 'absolute'; |
|
1065 | - } |
|
1066 | - |
|
1067 | - $this->includePatterns[$code][(string) $pattern] = (string) $pattern['type']; |
|
1068 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1069 | - echo str_repeat("\t", $depth); |
|
1070 | - echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern'; |
|
1071 | - if ($code !== $ref) { |
|
1072 | - echo " for $code"; |
|
1073 | - } |
|
1074 | - |
|
1075 | - echo ': '.(string) $pattern.PHP_EOL; |
|
1076 | - } |
|
1077 | - }//end foreach |
|
1078 | - }//end foreach |
|
1079 | - |
|
1080 | - }//end processRule() |
|
1081 | - |
|
1082 | - |
|
1083 | - /** |
|
1084 | - * Determine if an element should be processed or ignored. |
|
1085 | - * |
|
1086 | - * @param \SimpleXMLElement $element An object from a ruleset XML file. |
|
1087 | - * |
|
1088 | - * @return bool |
|
1089 | - */ |
|
1090 | - private function shouldProcessElement($element) |
|
1091 | - { |
|
1092 | - if (isset($element['phpcbf-only']) === false |
|
1093 | - && isset($element['phpcs-only']) === false |
|
1094 | - ) { |
|
1095 | - // No exceptions are being made. |
|
1096 | - return true; |
|
1097 | - } |
|
1098 | - |
|
1099 | - if (PHP_CODESNIFFER_CBF === true |
|
1100 | - && isset($element['phpcbf-only']) === true |
|
1101 | - && (string) $element['phpcbf-only'] === 'true' |
|
1102 | - ) { |
|
1103 | - return true; |
|
1104 | - } |
|
1105 | - |
|
1106 | - if (PHP_CODESNIFFER_CBF === false |
|
1107 | - && isset($element['phpcs-only']) === true |
|
1108 | - && (string) $element['phpcs-only'] === 'true' |
|
1109 | - ) { |
|
1110 | - return true; |
|
1111 | - } |
|
1112 | - |
|
1113 | - return false; |
|
1114 | - |
|
1115 | - }//end shouldProcessElement() |
|
1116 | - |
|
1117 | - |
|
1118 | - /** |
|
1119 | - * Loads and stores sniffs objects used for sniffing files. |
|
1120 | - * |
|
1121 | - * @param array $files Paths to the sniff files to register. |
|
1122 | - * @param array $restrictions The sniff class names to restrict the allowed |
|
1123 | - * listeners to. |
|
1124 | - * @param array $exclusions The sniff class names to exclude from the |
|
1125 | - * listeners list. |
|
1126 | - * |
|
1127 | - * @return void |
|
1128 | - */ |
|
1129 | - public function registerSniffs($files, $restrictions, $exclusions) |
|
1130 | - { |
|
1131 | - $listeners = []; |
|
1132 | - |
|
1133 | - foreach ($files as $file) { |
|
1134 | - // Work out where the position of /StandardName/Sniffs/... is |
|
1135 | - // so we can determine what the class will be called. |
|
1136 | - $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR); |
|
1137 | - if ($sniffPos === false) { |
|
1138 | - continue; |
|
1139 | - } |
|
1140 | - |
|
1141 | - $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR); |
|
1142 | - if ($slashPos === false) { |
|
1143 | - continue; |
|
1144 | - } |
|
1145 | - |
|
1146 | - $className = Autoload::loadFile($file); |
|
1147 | - $compareName = Util\Common::cleanSniffClass($className); |
|
1148 | - |
|
1149 | - // If they have specified a list of sniffs to restrict to, check |
|
1150 | - // to see if this sniff is allowed. |
|
1151 | - if (empty($restrictions) === false |
|
1152 | - && isset($restrictions[$compareName]) === false |
|
1153 | - ) { |
|
1154 | - continue; |
|
1155 | - } |
|
1156 | - |
|
1157 | - // If they have specified a list of sniffs to exclude, check |
|
1158 | - // to see if this sniff is allowed. |
|
1159 | - if (empty($exclusions) === false |
|
1160 | - && isset($exclusions[$compareName]) === true |
|
1161 | - ) { |
|
1162 | - continue; |
|
1163 | - } |
|
1164 | - |
|
1165 | - // Skip abstract classes. |
|
1166 | - $reflection = new \ReflectionClass($className); |
|
1167 | - if ($reflection->isAbstract() === true) { |
|
1168 | - continue; |
|
1169 | - } |
|
1170 | - |
|
1171 | - $listeners[$className] = $className; |
|
1172 | - |
|
1173 | - if (PHP_CODESNIFFER_VERBOSITY > 2) { |
|
1174 | - echo "Registered $className".PHP_EOL; |
|
1175 | - } |
|
1176 | - }//end foreach |
|
1177 | - |
|
1178 | - $this->sniffs = $listeners; |
|
1179 | - |
|
1180 | - }//end registerSniffs() |
|
1181 | - |
|
1182 | - |
|
1183 | - /** |
|
1184 | - * Populates the array of PHP_CodeSniffer_Sniff's for this file. |
|
1185 | - * |
|
1186 | - * @return void |
|
1187 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If sniff registration fails. |
|
1188 | - */ |
|
1189 | - public function populateTokenListeners() |
|
1190 | - { |
|
1191 | - // Construct a list of listeners indexed by token being listened for. |
|
1192 | - $this->tokenListeners = []; |
|
1193 | - |
|
1194 | - foreach ($this->sniffs as $sniffClass => $sniffObject) { |
|
1195 | - $this->sniffs[$sniffClass] = null; |
|
1196 | - $this->sniffs[$sniffClass] = new $sniffClass(); |
|
1197 | - |
|
1198 | - $sniffCode = Util\Common::getSniffCode($sniffClass); |
|
1199 | - $this->sniffCodes[$sniffCode] = $sniffClass; |
|
1200 | - |
|
1201 | - // Set custom properties. |
|
1202 | - if (isset($this->ruleset[$sniffCode]['properties']) === true) { |
|
1203 | - foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) { |
|
1204 | - $this->setSniffProperty($sniffClass, $name, $value); |
|
1205 | - } |
|
1206 | - } |
|
1207 | - |
|
1208 | - $tokenizers = []; |
|
1209 | - $vars = get_class_vars($sniffClass); |
|
1210 | - if (isset($vars['supportedTokenizers']) === true) { |
|
1211 | - foreach ($vars['supportedTokenizers'] as $tokenizer) { |
|
1212 | - $tokenizers[$tokenizer] = $tokenizer; |
|
1213 | - } |
|
1214 | - } else { |
|
1215 | - $tokenizers = ['PHP' => 'PHP']; |
|
1216 | - } |
|
1217 | - |
|
1218 | - $tokens = $this->sniffs[$sniffClass]->register(); |
|
1219 | - if (is_array($tokens) === false) { |
|
1220 | - $msg = "Sniff $sniffClass register() method must return an array"; |
|
1221 | - throw new RuntimeException($msg); |
|
1222 | - } |
|
1223 | - |
|
1224 | - $ignorePatterns = []; |
|
1225 | - $patterns = $this->getIgnorePatterns($sniffCode); |
|
1226 | - foreach ($patterns as $pattern => $type) { |
|
1227 | - $replacements = [ |
|
1228 | - '\\,' => ',', |
|
1229 | - '*' => '.*', |
|
1230 | - ]; |
|
1231 | - |
|
1232 | - $ignorePatterns[] = strtr($pattern, $replacements); |
|
1233 | - } |
|
1234 | - |
|
1235 | - $includePatterns = []; |
|
1236 | - $patterns = $this->getIncludePatterns($sniffCode); |
|
1237 | - foreach ($patterns as $pattern => $type) { |
|
1238 | - $replacements = [ |
|
1239 | - '\\,' => ',', |
|
1240 | - '*' => '.*', |
|
1241 | - ]; |
|
1242 | - |
|
1243 | - $includePatterns[] = strtr($pattern, $replacements); |
|
1244 | - } |
|
1245 | - |
|
1246 | - foreach ($tokens as $token) { |
|
1247 | - if (isset($this->tokenListeners[$token]) === false) { |
|
1248 | - $this->tokenListeners[$token] = []; |
|
1249 | - } |
|
1250 | - |
|
1251 | - if (isset($this->tokenListeners[$token][$sniffClass]) === false) { |
|
1252 | - $this->tokenListeners[$token][$sniffClass] = [ |
|
1253 | - 'class' => $sniffClass, |
|
1254 | - 'source' => $sniffCode, |
|
1255 | - 'tokenizers' => $tokenizers, |
|
1256 | - 'ignore' => $ignorePatterns, |
|
1257 | - 'include' => $includePatterns, |
|
1258 | - ]; |
|
1259 | - } |
|
1260 | - } |
|
1261 | - }//end foreach |
|
1262 | - |
|
1263 | - }//end populateTokenListeners() |
|
1264 | - |
|
1265 | - |
|
1266 | - /** |
|
1267 | - * Set a single property for a sniff. |
|
1268 | - * |
|
1269 | - * @param string $sniffClass The class name of the sniff. |
|
1270 | - * @param string $name The name of the property to change. |
|
1271 | - * @param string $value The new value of the property. |
|
1272 | - * |
|
1273 | - * @return void |
|
1274 | - */ |
|
1275 | - public function setSniffProperty($sniffClass, $name, $value) |
|
1276 | - { |
|
1277 | - // Setting a property for a sniff we are not using. |
|
1278 | - if (isset($this->sniffs[$sniffClass]) === false) { |
|
1279 | - return; |
|
1280 | - } |
|
1281 | - |
|
1282 | - $name = trim($name); |
|
1283 | - if (is_string($value) === true) { |
|
1284 | - $value = trim($value); |
|
1285 | - } |
|
1286 | - |
|
1287 | - if ($value === '') { |
|
1288 | - $value = null; |
|
1289 | - } |
|
1290 | - |
|
1291 | - // Special case for booleans. |
|
1292 | - if ($value === 'true') { |
|
1293 | - $value = true; |
|
1294 | - } else if ($value === 'false') { |
|
1295 | - $value = false; |
|
1296 | - } else if (substr($name, -2) === '[]') { |
|
1297 | - $name = substr($name, 0, -2); |
|
1298 | - $values = []; |
|
1299 | - if ($value !== null) { |
|
1300 | - foreach (explode(',', $value) as $val) { |
|
1301 | - list($k, $v) = explode('=>', $val.'=>'); |
|
1302 | - if ($v !== '') { |
|
1303 | - $values[trim($k)] = trim($v); |
|
1304 | - } else { |
|
1305 | - $values[] = trim($k); |
|
1306 | - } |
|
1307 | - } |
|
1308 | - } |
|
1309 | - |
|
1310 | - $value = $values; |
|
1311 | - } |
|
1312 | - |
|
1313 | - $this->sniffs[$sniffClass]->$name = $value; |
|
1314 | - |
|
1315 | - }//end setSniffProperty() |
|
1316 | - |
|
1317 | - |
|
1318 | - /** |
|
1319 | - * Gets the array of ignore patterns. |
|
1320 | - * |
|
1321 | - * Optionally takes a listener to get ignore patterns specified |
|
1322 | - * for that sniff only. |
|
1323 | - * |
|
1324 | - * @param string $listener The listener to get patterns for. If NULL, all |
|
1325 | - * patterns are returned. |
|
1326 | - * |
|
1327 | - * @return array |
|
1328 | - */ |
|
1329 | - public function getIgnorePatterns($listener=null) |
|
1330 | - { |
|
1331 | - if ($listener === null) { |
|
1332 | - return $this->ignorePatterns; |
|
1333 | - } |
|
1334 | - |
|
1335 | - if (isset($this->ignorePatterns[$listener]) === true) { |
|
1336 | - return $this->ignorePatterns[$listener]; |
|
1337 | - } |
|
1338 | - |
|
1339 | - return []; |
|
1340 | - |
|
1341 | - }//end getIgnorePatterns() |
|
1342 | - |
|
1343 | - |
|
1344 | - /** |
|
1345 | - * Gets the array of include patterns. |
|
1346 | - * |
|
1347 | - * Optionally takes a listener to get include patterns specified |
|
1348 | - * for that sniff only. |
|
1349 | - * |
|
1350 | - * @param string $listener The listener to get patterns for. If NULL, all |
|
1351 | - * patterns are returned. |
|
1352 | - * |
|
1353 | - * @return array |
|
1354 | - */ |
|
1355 | - public function getIncludePatterns($listener=null) |
|
1356 | - { |
|
1357 | - if ($listener === null) { |
|
1358 | - return $this->includePatterns; |
|
1359 | - } |
|
1360 | - |
|
1361 | - if (isset($this->includePatterns[$listener]) === true) { |
|
1362 | - return $this->includePatterns[$listener]; |
|
1363 | - } |
|
1364 | - |
|
1365 | - return []; |
|
1366 | - |
|
1367 | - }//end getIncludePatterns() |
|
20 | + /** |
|
21 | + * The name of the coding standard being used. |
|
22 | + * |
|
23 | + * If a top-level standard includes other standards, or sniffs |
|
24 | + * from other standards, only the name of the top-level standard |
|
25 | + * will be stored in here. |
|
26 | + * |
|
27 | + * If multiple top-level standards are being loaded into |
|
28 | + * a single ruleset object, this will store a comma separated list |
|
29 | + * of the top-level standard names. |
|
30 | + * |
|
31 | + * @var string |
|
32 | + */ |
|
33 | + public $name = ''; |
|
34 | + |
|
35 | + /** |
|
36 | + * A list of file paths for the ruleset files being used. |
|
37 | + * |
|
38 | + * @var string[] |
|
39 | + */ |
|
40 | + public $paths = []; |
|
41 | + |
|
42 | + /** |
|
43 | + * A list of regular expressions used to ignore specific sniffs for files and folders. |
|
44 | + * |
|
45 | + * Is also used to set global exclude patterns. |
|
46 | + * The key is the regular expression and the value is the type |
|
47 | + * of ignore pattern (absolute or relative). |
|
48 | + * |
|
49 | + * @var array<string, string> |
|
50 | + */ |
|
51 | + public $ignorePatterns = []; |
|
52 | + |
|
53 | + /** |
|
54 | + * A list of regular expressions used to include specific sniffs for files and folders. |
|
55 | + * |
|
56 | + * The key is the sniff code and the value is an array with |
|
57 | + * the key being a regular expression and the value is the type |
|
58 | + * of ignore pattern (absolute or relative). |
|
59 | + * |
|
60 | + * @var array<string, array<string, string>> |
|
61 | + */ |
|
62 | + public $includePatterns = []; |
|
63 | + |
|
64 | + /** |
|
65 | + * An array of sniff objects that are being used to check files. |
|
66 | + * |
|
67 | + * The key is the fully qualified name of the sniff class |
|
68 | + * and the value is the sniff object. |
|
69 | + * |
|
70 | + * @var array<string, \PHP_CodeSniffer\Sniffs\Sniff> |
|
71 | + */ |
|
72 | + public $sniffs = []; |
|
73 | + |
|
74 | + /** |
|
75 | + * A mapping of sniff codes to fully qualified class names. |
|
76 | + * |
|
77 | + * The key is the sniff code and the value |
|
78 | + * is the fully qualified name of the sniff class. |
|
79 | + * |
|
80 | + * @var array<string, string> |
|
81 | + */ |
|
82 | + public $sniffCodes = []; |
|
83 | + |
|
84 | + /** |
|
85 | + * An array of token types and the sniffs that are listening for them. |
|
86 | + * |
|
87 | + * The key is the token name being listened for and the value |
|
88 | + * is the sniff object. |
|
89 | + * |
|
90 | + * @var array<int, \PHP_CodeSniffer\Sniffs\Sniff> |
|
91 | + */ |
|
92 | + public $tokenListeners = []; |
|
93 | + |
|
94 | + /** |
|
95 | + * An array of rules from the ruleset.xml file. |
|
96 | + * |
|
97 | + * It may be empty, indicating that the ruleset does not override |
|
98 | + * any of the default sniff settings. |
|
99 | + * |
|
100 | + * @var array<string, mixed> |
|
101 | + */ |
|
102 | + public $ruleset = []; |
|
103 | + |
|
104 | + /** |
|
105 | + * The directories that the processed rulesets are in. |
|
106 | + * |
|
107 | + * @var string[] |
|
108 | + */ |
|
109 | + protected $rulesetDirs = []; |
|
110 | + |
|
111 | + /** |
|
112 | + * The config data for the run. |
|
113 | + * |
|
114 | + * @var \PHP_CodeSniffer\Config |
|
115 | + */ |
|
116 | + private $config = null; |
|
117 | + |
|
118 | + |
|
119 | + /** |
|
120 | + * Initialise the ruleset that the run will use. |
|
121 | + * |
|
122 | + * @param \PHP_CodeSniffer\Config $config The config data for the run. |
|
123 | + * |
|
124 | + * @return void |
|
125 | + */ |
|
126 | + public function __construct(Config $config) |
|
127 | + { |
|
128 | + // Ignore sniff restrictions if caching is on. |
|
129 | + $restrictions = []; |
|
130 | + $exclusions = []; |
|
131 | + if ($config->cache === false) { |
|
132 | + $restrictions = $config->sniffs; |
|
133 | + $exclusions = $config->exclude; |
|
134 | + } |
|
135 | + |
|
136 | + $this->config = $config; |
|
137 | + $sniffs = []; |
|
138 | + |
|
139 | + $standardPaths = []; |
|
140 | + foreach ($config->standards as $standard) { |
|
141 | + $installed = Util\Standards::getInstalledStandardPath($standard); |
|
142 | + if ($installed === null) { |
|
143 | + $standard = Util\Common::realpath($standard); |
|
144 | + if (is_dir($standard) === true |
|
145 | + && is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true |
|
146 | + ) { |
|
147 | + $standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml'); |
|
148 | + } |
|
149 | + } else { |
|
150 | + $standard = $installed; |
|
151 | + } |
|
152 | + |
|
153 | + $standardPaths[] = $standard; |
|
154 | + } |
|
155 | + |
|
156 | + foreach ($standardPaths as $standard) { |
|
157 | + $ruleset = @simplexml_load_string(file_get_contents($standard)); |
|
158 | + if ($ruleset !== false) { |
|
159 | + $standardName = (string) $ruleset['name']; |
|
160 | + if ($this->name !== '') { |
|
161 | + $this->name .= ', '; |
|
162 | + } |
|
163 | + |
|
164 | + $this->name .= $standardName; |
|
165 | + |
|
166 | + // Allow autoloading of custom files inside this standard. |
|
167 | + if (isset($ruleset['namespace']) === true) { |
|
168 | + $namespace = (string) $ruleset['namespace']; |
|
169 | + } else { |
|
170 | + $namespace = basename(dirname($standard)); |
|
171 | + } |
|
172 | + |
|
173 | + Autoload::addSearchPath(dirname($standard), $namespace); |
|
174 | + } |
|
175 | + |
|
176 | + if (defined('PHP_CODESNIFFER_IN_TESTS') === true && empty($restrictions) === false) { |
|
177 | + // In unit tests, only register the sniffs that the test wants and not the entire standard. |
|
178 | + try { |
|
179 | + foreach ($restrictions as $restriction) { |
|
180 | + $sniffs = array_merge($sniffs, $this->expandRulesetReference($restriction, dirname($standard))); |
|
181 | + } |
|
182 | + } catch (RuntimeException $e) { |
|
183 | + // Sniff reference could not be expanded, which probably means this |
|
184 | + // is an installed standard. Let the unit test system take care of |
|
185 | + // setting the correct sniff for testing. |
|
186 | + return; |
|
187 | + } |
|
188 | + |
|
189 | + break; |
|
190 | + } |
|
191 | + |
|
192 | + if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
193 | + echo "Registering sniffs in the $standardName standard... "; |
|
194 | + if (count($config->standards) > 1 || PHP_CODESNIFFER_VERBOSITY > 2) { |
|
195 | + echo PHP_EOL; |
|
196 | + } |
|
197 | + } |
|
198 | + |
|
199 | + $sniffs = array_merge($sniffs, $this->processRuleset($standard)); |
|
200 | + }//end foreach |
|
201 | + |
|
202 | + $sniffRestrictions = []; |
|
203 | + foreach ($restrictions as $sniffCode) { |
|
204 | + $parts = explode('.', strtolower($sniffCode)); |
|
205 | + $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; |
|
206 | + $sniffRestrictions[$sniffName] = true; |
|
207 | + } |
|
208 | + |
|
209 | + $sniffExclusions = []; |
|
210 | + foreach ($exclusions as $sniffCode) { |
|
211 | + $parts = explode('.', strtolower($sniffCode)); |
|
212 | + $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; |
|
213 | + $sniffExclusions[$sniffName] = true; |
|
214 | + } |
|
215 | + |
|
216 | + $this->registerSniffs($sniffs, $sniffRestrictions, $sniffExclusions); |
|
217 | + $this->populateTokenListeners(); |
|
218 | + |
|
219 | + $numSniffs = count($this->sniffs); |
|
220 | + if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
221 | + echo "DONE ($numSniffs sniffs registered)".PHP_EOL; |
|
222 | + } |
|
223 | + |
|
224 | + if ($numSniffs === 0) { |
|
225 | + throw new RuntimeException('No sniffs were registered'); |
|
226 | + } |
|
227 | + |
|
228 | + }//end __construct() |
|
229 | + |
|
230 | + |
|
231 | + /** |
|
232 | + * Prints a report showing the sniffs contained in a standard. |
|
233 | + * |
|
234 | + * @return void |
|
235 | + */ |
|
236 | + public function explain() |
|
237 | + { |
|
238 | + $sniffs = array_keys($this->sniffCodes); |
|
239 | + sort($sniffs); |
|
240 | + |
|
241 | + ob_start(); |
|
242 | + |
|
243 | + $lastStandard = null; |
|
244 | + $lastCount = ''; |
|
245 | + $sniffCount = count($sniffs); |
|
246 | + |
|
247 | + // Add a dummy entry to the end so we loop |
|
248 | + // one last time and clear the output buffer. |
|
249 | + $sniffs[] = ''; |
|
250 | + |
|
251 | + echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL; |
|
252 | + |
|
253 | + ob_start(); |
|
254 | + |
|
255 | + foreach ($sniffs as $i => $sniff) { |
|
256 | + if ($i === $sniffCount) { |
|
257 | + $currentStandard = null; |
|
258 | + } else { |
|
259 | + $currentStandard = substr($sniff, 0, strpos($sniff, '.')); |
|
260 | + if ($lastStandard === null) { |
|
261 | + $lastStandard = $currentStandard; |
|
262 | + } |
|
263 | + } |
|
264 | + |
|
265 | + if ($currentStandard !== $lastStandard) { |
|
266 | + $sniffList = ob_get_contents(); |
|
267 | + ob_end_clean(); |
|
268 | + |
|
269 | + echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff'; |
|
270 | + if ($lastCount > 1) { |
|
271 | + echo 's'; |
|
272 | + } |
|
273 | + |
|
274 | + echo ')'.PHP_EOL; |
|
275 | + echo str_repeat('-', (strlen($lastStandard.$lastCount) + 10)); |
|
276 | + echo PHP_EOL; |
|
277 | + echo $sniffList; |
|
278 | + |
|
279 | + $lastStandard = $currentStandard; |
|
280 | + $lastCount = 0; |
|
281 | + |
|
282 | + if ($currentStandard === null) { |
|
283 | + break; |
|
284 | + } |
|
285 | + |
|
286 | + ob_start(); |
|
287 | + }//end if |
|
288 | + |
|
289 | + echo ' '.$sniff.PHP_EOL; |
|
290 | + $lastCount++; |
|
291 | + }//end foreach |
|
292 | + |
|
293 | + }//end explain() |
|
294 | + |
|
295 | + |
|
296 | + /** |
|
297 | + * Processes a single ruleset and returns a list of the sniffs it represents. |
|
298 | + * |
|
299 | + * Rules founds within the ruleset are processed immediately, but sniff classes |
|
300 | + * are not registered by this method. |
|
301 | + * |
|
302 | + * @param string $rulesetPath The path to a ruleset XML file. |
|
303 | + * @param int $depth How many nested processing steps we are in. This |
|
304 | + * is only used for debug output. |
|
305 | + * |
|
306 | + * @return string[] |
|
307 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the ruleset path is invalid. |
|
308 | + */ |
|
309 | + public function processRuleset($rulesetPath, $depth=0) |
|
310 | + { |
|
311 | + $rulesetPath = Util\Common::realpath($rulesetPath); |
|
312 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
313 | + echo str_repeat("\t", $depth); |
|
314 | + echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath, $this->config->basepath).PHP_EOL; |
|
315 | + } |
|
316 | + |
|
317 | + libxml_use_internal_errors(true); |
|
318 | + $ruleset = simplexml_load_string(file_get_contents($rulesetPath)); |
|
319 | + if ($ruleset === false) { |
|
320 | + $errorMsg = "Ruleset $rulesetPath is not valid".PHP_EOL; |
|
321 | + $errors = libxml_get_errors(); |
|
322 | + foreach ($errors as $error) { |
|
323 | + $errorMsg .= '- On line '.$error->line.', column '.$error->column.': '.$error->message; |
|
324 | + } |
|
325 | + |
|
326 | + libxml_clear_errors(); |
|
327 | + throw new RuntimeException($errorMsg); |
|
328 | + } |
|
329 | + |
|
330 | + libxml_use_internal_errors(false); |
|
331 | + |
|
332 | + $ownSniffs = []; |
|
333 | + $includedSniffs = []; |
|
334 | + $excludedSniffs = []; |
|
335 | + |
|
336 | + $this->paths[] = $rulesetPath; |
|
337 | + $rulesetDir = dirname($rulesetPath); |
|
338 | + $this->rulesetDirs[] = $rulesetDir; |
|
339 | + |
|
340 | + $sniffDir = $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs'; |
|
341 | + if (is_dir($sniffDir) === true) { |
|
342 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
343 | + echo str_repeat("\t", $depth); |
|
344 | + echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir, $this->config->basepath).' directory'.PHP_EOL; |
|
345 | + } |
|
346 | + |
|
347 | + $ownSniffs = $this->expandSniffDirectory($sniffDir, $depth); |
|
348 | + } |
|
349 | + |
|
350 | + // Included custom autoloaders. |
|
351 | + foreach ($ruleset->{'autoload'} as $autoload) { |
|
352 | + if ($this->shouldProcessElement($autoload) === false) { |
|
353 | + continue; |
|
354 | + } |
|
355 | + |
|
356 | + $autoloadPath = (string) $autoload; |
|
357 | + if (is_file($autoloadPath) === false) { |
|
358 | + $autoloadPath = Util\Common::realPath(dirname($rulesetPath).DIRECTORY_SEPARATOR.$autoloadPath); |
|
359 | + } |
|
360 | + |
|
361 | + if ($autoloadPath === false) { |
|
362 | + throw new RuntimeException('The specified autoload file "'.$autoload.'" does not exist'); |
|
363 | + } |
|
364 | + |
|
365 | + include_once $autoloadPath; |
|
366 | + |
|
367 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
368 | + echo str_repeat("\t", $depth); |
|
369 | + echo "\t=> included autoloader $autoloadPath".PHP_EOL; |
|
370 | + } |
|
371 | + }//end foreach |
|
372 | + |
|
373 | + // Process custom sniff config settings. |
|
374 | + foreach ($ruleset->{'config'} as $config) { |
|
375 | + if ($this->shouldProcessElement($config) === false) { |
|
376 | + continue; |
|
377 | + } |
|
378 | + |
|
379 | + Config::setConfigData((string) $config['name'], (string) $config['value'], true); |
|
380 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
381 | + echo str_repeat("\t", $depth); |
|
382 | + echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL; |
|
383 | + } |
|
384 | + } |
|
385 | + |
|
386 | + foreach ($ruleset->rule as $rule) { |
|
387 | + if (isset($rule['ref']) === false |
|
388 | + || $this->shouldProcessElement($rule) === false |
|
389 | + ) { |
|
390 | + continue; |
|
391 | + } |
|
392 | + |
|
393 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
394 | + echo str_repeat("\t", $depth); |
|
395 | + echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL; |
|
396 | + } |
|
397 | + |
|
398 | + $expandedSniffs = $this->expandRulesetReference((string) $rule['ref'], $rulesetDir, $depth); |
|
399 | + $newSniffs = array_diff($expandedSniffs, $includedSniffs); |
|
400 | + $includedSniffs = array_merge($includedSniffs, $expandedSniffs); |
|
401 | + |
|
402 | + $parts = explode('.', $rule['ref']); |
|
403 | + if (count($parts) === 4 |
|
404 | + && $parts[0] !== '' |
|
405 | + && $parts[1] !== '' |
|
406 | + && $parts[2] !== '' |
|
407 | + ) { |
|
408 | + $sniffCode = $parts[0].'.'.$parts[1].'.'.$parts[2]; |
|
409 | + if (isset($this->ruleset[$sniffCode]['severity']) === true |
|
410 | + && $this->ruleset[$sniffCode]['severity'] === 0 |
|
411 | + ) { |
|
412 | + // This sniff code has already been turned off, but now |
|
413 | + // it is being explicitly included again, so turn it back on. |
|
414 | + $this->ruleset[(string) $rule['ref']]['severity'] = 5; |
|
415 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
416 | + echo str_repeat("\t", $depth); |
|
417 | + echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL; |
|
418 | + echo str_repeat("\t", $depth); |
|
419 | + echo "\t\t=> severity set to 5".PHP_EOL; |
|
420 | + } |
|
421 | + } else if (empty($newSniffs) === false) { |
|
422 | + $newSniff = $newSniffs[0]; |
|
423 | + if (in_array($newSniff, $ownSniffs, true) === false) { |
|
424 | + // Including a sniff that hasn't been included higher up, but |
|
425 | + // only including a single message from it. So turn off all messages in |
|
426 | + // the sniff, except this one. |
|
427 | + $this->ruleset[$sniffCode]['severity'] = 0; |
|
428 | + $this->ruleset[(string) $rule['ref']]['severity'] = 5; |
|
429 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
430 | + echo str_repeat("\t", $depth); |
|
431 | + echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL; |
|
432 | + } |
|
433 | + } |
|
434 | + }//end if |
|
435 | + }//end if |
|
436 | + |
|
437 | + if (isset($rule->exclude) === true) { |
|
438 | + foreach ($rule->exclude as $exclude) { |
|
439 | + if (isset($exclude['name']) === false) { |
|
440 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
441 | + echo str_repeat("\t", $depth); |
|
442 | + echo "\t\t* ignoring empty exclude rule *".PHP_EOL; |
|
443 | + echo "\t\t\t=> ".$exclude->asXML().PHP_EOL; |
|
444 | + } |
|
445 | + |
|
446 | + continue; |
|
447 | + } |
|
448 | + |
|
449 | + if ($this->shouldProcessElement($exclude) === false) { |
|
450 | + continue; |
|
451 | + } |
|
452 | + |
|
453 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
454 | + echo str_repeat("\t", $depth); |
|
455 | + echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL; |
|
456 | + } |
|
457 | + |
|
458 | + // Check if a single code is being excluded, which is a shortcut |
|
459 | + // for setting the severity of the message to 0. |
|
460 | + $parts = explode('.', $exclude['name']); |
|
461 | + if (count($parts) === 4) { |
|
462 | + $this->ruleset[(string) $exclude['name']]['severity'] = 0; |
|
463 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
464 | + echo str_repeat("\t", $depth); |
|
465 | + echo "\t\t=> severity set to 0".PHP_EOL; |
|
466 | + } |
|
467 | + } else { |
|
468 | + $excludedSniffs = array_merge( |
|
469 | + $excludedSniffs, |
|
470 | + $this->expandRulesetReference((string) $exclude['name'], $rulesetDir, ($depth + 1)) |
|
471 | + ); |
|
472 | + } |
|
473 | + }//end foreach |
|
474 | + }//end if |
|
475 | + |
|
476 | + $this->processRule($rule, $newSniffs, $depth); |
|
477 | + }//end foreach |
|
478 | + |
|
479 | + // Process custom command line arguments. |
|
480 | + $cliArgs = []; |
|
481 | + foreach ($ruleset->{'arg'} as $arg) { |
|
482 | + if ($this->shouldProcessElement($arg) === false) { |
|
483 | + continue; |
|
484 | + } |
|
485 | + |
|
486 | + if (isset($arg['name']) === true) { |
|
487 | + $argString = '--'.(string) $arg['name']; |
|
488 | + if (isset($arg['value']) === true) { |
|
489 | + $argString .= '='.(string) $arg['value']; |
|
490 | + } |
|
491 | + } else { |
|
492 | + $argString = '-'.(string) $arg['value']; |
|
493 | + } |
|
494 | + |
|
495 | + $cliArgs[] = $argString; |
|
496 | + |
|
497 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
498 | + echo str_repeat("\t", $depth); |
|
499 | + echo "\t=> set command line value $argString".PHP_EOL; |
|
500 | + } |
|
501 | + }//end foreach |
|
502 | + |
|
503 | + // Set custom php ini values as CLI args. |
|
504 | + foreach ($ruleset->{'ini'} as $arg) { |
|
505 | + if ($this->shouldProcessElement($arg) === false) { |
|
506 | + continue; |
|
507 | + } |
|
508 | + |
|
509 | + if (isset($arg['name']) === false) { |
|
510 | + continue; |
|
511 | + } |
|
512 | + |
|
513 | + $name = (string) $arg['name']; |
|
514 | + $argString = $name; |
|
515 | + if (isset($arg['value']) === true) { |
|
516 | + $value = (string) $arg['value']; |
|
517 | + $argString .= "=$value"; |
|
518 | + } else { |
|
519 | + $value = 'true'; |
|
520 | + } |
|
521 | + |
|
522 | + $cliArgs[] = '-d'; |
|
523 | + $cliArgs[] = $argString; |
|
524 | + |
|
525 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
526 | + echo str_repeat("\t", $depth); |
|
527 | + echo "\t=> set PHP ini value $name to $value".PHP_EOL; |
|
528 | + } |
|
529 | + }//end foreach |
|
530 | + |
|
531 | + if (empty($this->config->files) === true) { |
|
532 | + // Process hard-coded file paths. |
|
533 | + foreach ($ruleset->{'file'} as $file) { |
|
534 | + $file = (string) $file; |
|
535 | + $cliArgs[] = $file; |
|
536 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
537 | + echo str_repeat("\t", $depth); |
|
538 | + echo "\t=> added \"$file\" to the file list".PHP_EOL; |
|
539 | + } |
|
540 | + } |
|
541 | + } |
|
542 | + |
|
543 | + if (empty($cliArgs) === false) { |
|
544 | + // Change the directory so all relative paths are worked |
|
545 | + // out based on the location of the ruleset instead of |
|
546 | + // the location of the user. |
|
547 | + $inPhar = Util\Common::isPharFile($rulesetDir); |
|
548 | + if ($inPhar === false) { |
|
549 | + $currentDir = getcwd(); |
|
550 | + chdir($rulesetDir); |
|
551 | + } |
|
552 | + |
|
553 | + $this->config->setCommandLineValues($cliArgs); |
|
554 | + |
|
555 | + if ($inPhar === false) { |
|
556 | + chdir($currentDir); |
|
557 | + } |
|
558 | + } |
|
559 | + |
|
560 | + // Process custom ignore pattern rules. |
|
561 | + foreach ($ruleset->{'exclude-pattern'} as $pattern) { |
|
562 | + if ($this->shouldProcessElement($pattern) === false) { |
|
563 | + continue; |
|
564 | + } |
|
565 | + |
|
566 | + if (isset($pattern['type']) === false) { |
|
567 | + $pattern['type'] = 'absolute'; |
|
568 | + } |
|
569 | + |
|
570 | + $this->ignorePatterns[(string) $pattern] = (string) $pattern['type']; |
|
571 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
572 | + echo str_repeat("\t", $depth); |
|
573 | + echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL; |
|
574 | + } |
|
575 | + } |
|
576 | + |
|
577 | + $includedSniffs = array_unique(array_merge($ownSniffs, $includedSniffs)); |
|
578 | + $excludedSniffs = array_unique($excludedSniffs); |
|
579 | + |
|
580 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
581 | + $included = count($includedSniffs); |
|
582 | + $excluded = count($excludedSniffs); |
|
583 | + echo str_repeat("\t", $depth); |
|
584 | + echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL; |
|
585 | + } |
|
586 | + |
|
587 | + // Merge our own sniff list with our externally included |
|
588 | + // sniff list, but filter out any excluded sniffs. |
|
589 | + $files = []; |
|
590 | + foreach ($includedSniffs as $sniff) { |
|
591 | + if (in_array($sniff, $excludedSniffs, true) === true) { |
|
592 | + continue; |
|
593 | + } else { |
|
594 | + $files[] = Util\Common::realpath($sniff); |
|
595 | + } |
|
596 | + } |
|
597 | + |
|
598 | + return $files; |
|
599 | + |
|
600 | + }//end processRuleset() |
|
601 | + |
|
602 | + |
|
603 | + /** |
|
604 | + * Expands a directory into a list of sniff files within. |
|
605 | + * |
|
606 | + * @param string $directory The path to a directory. |
|
607 | + * @param int $depth How many nested processing steps we are in. This |
|
608 | + * is only used for debug output. |
|
609 | + * |
|
610 | + * @return array |
|
611 | + */ |
|
612 | + private function expandSniffDirectory($directory, $depth=0) |
|
613 | + { |
|
614 | + $sniffs = []; |
|
615 | + |
|
616 | + $rdi = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); |
|
617 | + $di = new \RecursiveIteratorIterator($rdi, 0, \RecursiveIteratorIterator::CATCH_GET_CHILD); |
|
618 | + |
|
619 | + $dirLen = strlen($directory); |
|
620 | + |
|
621 | + foreach ($di as $file) { |
|
622 | + $filename = $file->getFilename(); |
|
623 | + |
|
624 | + // Skip hidden files. |
|
625 | + if (substr($filename, 0, 1) === '.') { |
|
626 | + continue; |
|
627 | + } |
|
628 | + |
|
629 | + // We are only interested in PHP and sniff files. |
|
630 | + $fileParts = explode('.', $filename); |
|
631 | + if (array_pop($fileParts) !== 'php') { |
|
632 | + continue; |
|
633 | + } |
|
634 | + |
|
635 | + $basename = basename($filename, '.php'); |
|
636 | + if (substr($basename, -5) !== 'Sniff') { |
|
637 | + continue; |
|
638 | + } |
|
639 | + |
|
640 | + $path = $file->getPathname(); |
|
641 | + |
|
642 | + // Skip files in hidden directories within the Sniffs directory of this |
|
643 | + // standard. We use the offset with strpos() to allow hidden directories |
|
644 | + // before, valid example: |
|
645 | + // /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/... |
|
646 | + if (strpos($path, DIRECTORY_SEPARATOR.'.', $dirLen) !== false) { |
|
647 | + continue; |
|
648 | + } |
|
649 | + |
|
650 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
651 | + echo str_repeat("\t", $depth); |
|
652 | + echo "\t\t=> ".Util\Common::stripBasepath($path, $this->config->basepath).PHP_EOL; |
|
653 | + } |
|
654 | + |
|
655 | + $sniffs[] = $path; |
|
656 | + }//end foreach |
|
657 | + |
|
658 | + return $sniffs; |
|
659 | + |
|
660 | + }//end expandSniffDirectory() |
|
661 | + |
|
662 | + |
|
663 | + /** |
|
664 | + * Expands a ruleset reference into a list of sniff files. |
|
665 | + * |
|
666 | + * @param string $ref The reference from the ruleset XML file. |
|
667 | + * @param string $rulesetDir The directory of the ruleset XML file, used to |
|
668 | + * evaluate relative paths. |
|
669 | + * @param int $depth How many nested processing steps we are in. This |
|
670 | + * is only used for debug output. |
|
671 | + * |
|
672 | + * @return array |
|
673 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the reference is invalid. |
|
674 | + */ |
|
675 | + private function expandRulesetReference($ref, $rulesetDir, $depth=0) |
|
676 | + { |
|
677 | + // Ignore internal sniffs codes as they are used to only |
|
678 | + // hide and change internal messages. |
|
679 | + if (substr($ref, 0, 9) === 'Internal.') { |
|
680 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
681 | + echo str_repeat("\t", $depth); |
|
682 | + echo "\t\t* ignoring internal sniff code *".PHP_EOL; |
|
683 | + } |
|
684 | + |
|
685 | + return []; |
|
686 | + } |
|
687 | + |
|
688 | + // As sniffs can't begin with a full stop, assume references in |
|
689 | + // this format are relative paths and attempt to convert them |
|
690 | + // to absolute paths. If this fails, let the reference run through |
|
691 | + // the normal checks and have it fail as normal. |
|
692 | + if (substr($ref, 0, 1) === '.') { |
|
693 | + $realpath = Util\Common::realpath($rulesetDir.'/'.$ref); |
|
694 | + if ($realpath !== false) { |
|
695 | + $ref = $realpath; |
|
696 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
697 | + echo str_repeat("\t", $depth); |
|
698 | + echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
699 | + } |
|
700 | + } |
|
701 | + } |
|
702 | + |
|
703 | + // As sniffs can't begin with a tilde, assume references in |
|
704 | + // this format are relative to the user's home directory. |
|
705 | + if (substr($ref, 0, 2) === '~/') { |
|
706 | + $realpath = Util\Common::realpath($ref); |
|
707 | + if ($realpath !== false) { |
|
708 | + $ref = $realpath; |
|
709 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
710 | + echo str_repeat("\t", $depth); |
|
711 | + echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
712 | + } |
|
713 | + } |
|
714 | + } |
|
715 | + |
|
716 | + if (is_file($ref) === true) { |
|
717 | + if (substr($ref, -9) === 'Sniff.php') { |
|
718 | + // A single external sniff. |
|
719 | + $this->rulesetDirs[] = dirname(dirname(dirname($ref))); |
|
720 | + return [$ref]; |
|
721 | + } |
|
722 | + } else { |
|
723 | + // See if this is a whole standard being referenced. |
|
724 | + $path = Util\Standards::getInstalledStandardPath($ref); |
|
725 | + if (Util\Common::isPharFile($path) === true && strpos($path, 'ruleset.xml') === false) { |
|
726 | + // If the ruleset exists inside the phar file, use it. |
|
727 | + if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { |
|
728 | + $path .= DIRECTORY_SEPARATOR.'ruleset.xml'; |
|
729 | + } else { |
|
730 | + $path = null; |
|
731 | + } |
|
732 | + } |
|
733 | + |
|
734 | + if ($path !== null) { |
|
735 | + $ref = $path; |
|
736 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
737 | + echo str_repeat("\t", $depth); |
|
738 | + echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
739 | + } |
|
740 | + } else if (is_dir($ref) === false) { |
|
741 | + // Work out the sniff path. |
|
742 | + $sepPos = strpos($ref, DIRECTORY_SEPARATOR); |
|
743 | + if ($sepPos !== false) { |
|
744 | + $stdName = substr($ref, 0, $sepPos); |
|
745 | + $path = substr($ref, $sepPos); |
|
746 | + } else { |
|
747 | + $parts = explode('.', $ref); |
|
748 | + $stdName = $parts[0]; |
|
749 | + if (count($parts) === 1) { |
|
750 | + // A whole standard? |
|
751 | + $path = ''; |
|
752 | + } else if (count($parts) === 2) { |
|
753 | + // A directory of sniffs? |
|
754 | + $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1]; |
|
755 | + } else { |
|
756 | + // A single sniff? |
|
757 | + $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php'; |
|
758 | + } |
|
759 | + } |
|
760 | + |
|
761 | + $newRef = false; |
|
762 | + $stdPath = Util\Standards::getInstalledStandardPath($stdName); |
|
763 | + if ($stdPath !== null && $path !== '') { |
|
764 | + if (Util\Common::isPharFile($stdPath) === true |
|
765 | + && strpos($stdPath, 'ruleset.xml') === false |
|
766 | + ) { |
|
767 | + // Phar files can only return the directory, |
|
768 | + // since ruleset can be omitted if building one standard. |
|
769 | + $newRef = Util\Common::realpath($stdPath.$path); |
|
770 | + } else { |
|
771 | + $newRef = Util\Common::realpath(dirname($stdPath).$path); |
|
772 | + } |
|
773 | + } |
|
774 | + |
|
775 | + if ($newRef === false) { |
|
776 | + // The sniff is not locally installed, so check if it is being |
|
777 | + // referenced as a remote sniff outside the install. We do this |
|
778 | + // by looking through all directories where we have found ruleset |
|
779 | + // files before, looking for ones for this particular standard, |
|
780 | + // and seeing if it is in there. |
|
781 | + foreach ($this->rulesetDirs as $dir) { |
|
782 | + if (strtolower(basename($dir)) !== strtolower($stdName)) { |
|
783 | + continue; |
|
784 | + } |
|
785 | + |
|
786 | + $newRef = Util\Common::realpath($dir.$path); |
|
787 | + |
|
788 | + if ($newRef !== false) { |
|
789 | + $ref = $newRef; |
|
790 | + } |
|
791 | + } |
|
792 | + } else { |
|
793 | + $ref = $newRef; |
|
794 | + } |
|
795 | + |
|
796 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
797 | + echo str_repeat("\t", $depth); |
|
798 | + echo "\t\t=> ".Util\Common::stripBasepath($ref, $this->config->basepath).PHP_EOL; |
|
799 | + } |
|
800 | + }//end if |
|
801 | + }//end if |
|
802 | + |
|
803 | + if (is_dir($ref) === true) { |
|
804 | + if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml') === true) { |
|
805 | + // We are referencing an external coding standard. |
|
806 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
807 | + echo str_repeat("\t", $depth); |
|
808 | + echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL; |
|
809 | + } |
|
810 | + |
|
811 | + return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml', ($depth + 2)); |
|
812 | + } else { |
|
813 | + // We are referencing a whole directory of sniffs. |
|
814 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
815 | + echo str_repeat("\t", $depth); |
|
816 | + echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL; |
|
817 | + echo str_repeat("\t", $depth); |
|
818 | + echo "\t\tAdding sniff files from directory".PHP_EOL; |
|
819 | + } |
|
820 | + |
|
821 | + return $this->expandSniffDirectory($ref, ($depth + 1)); |
|
822 | + } |
|
823 | + } else { |
|
824 | + if (is_file($ref) === false) { |
|
825 | + $error = "Referenced sniff \"$ref\" does not exist"; |
|
826 | + throw new RuntimeException($error); |
|
827 | + } |
|
828 | + |
|
829 | + if (substr($ref, -9) === 'Sniff.php') { |
|
830 | + // A single sniff. |
|
831 | + return [$ref]; |
|
832 | + } else { |
|
833 | + // Assume an external ruleset.xml file. |
|
834 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
835 | + echo str_repeat("\t", $depth); |
|
836 | + echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL; |
|
837 | + } |
|
838 | + |
|
839 | + return $this->processRuleset($ref, ($depth + 2)); |
|
840 | + } |
|
841 | + }//end if |
|
842 | + |
|
843 | + }//end expandRulesetReference() |
|
844 | + |
|
845 | + |
|
846 | + /** |
|
847 | + * Processes a rule from a ruleset XML file, overriding built-in defaults. |
|
848 | + * |
|
849 | + * @param \SimpleXMLElement $rule The rule object from a ruleset XML file. |
|
850 | + * @param string[] $newSniffs An array of sniffs that got included by this rule. |
|
851 | + * @param int $depth How many nested processing steps we are in. |
|
852 | + * This is only used for debug output. |
|
853 | + * |
|
854 | + * @return void |
|
855 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If rule settings are invalid. |
|
856 | + */ |
|
857 | + private function processRule($rule, $newSniffs, $depth=0) |
|
858 | + { |
|
859 | + $ref = (string) $rule['ref']; |
|
860 | + $todo = [$ref]; |
|
861 | + |
|
862 | + $parts = explode('.', $ref); |
|
863 | + if (count($parts) <= 2) { |
|
864 | + // We are processing a standard or a category of sniffs. |
|
865 | + foreach ($newSniffs as $sniffFile) { |
|
866 | + $parts = explode(DIRECTORY_SEPARATOR, $sniffFile); |
|
867 | + $sniffName = array_pop($parts); |
|
868 | + $sniffCategory = array_pop($parts); |
|
869 | + array_pop($parts); |
|
870 | + $sniffStandard = array_pop($parts); |
|
871 | + $todo[] = $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName, 0, -9); |
|
872 | + } |
|
873 | + } |
|
874 | + |
|
875 | + foreach ($todo as $code) { |
|
876 | + // Custom severity. |
|
877 | + if (isset($rule->severity) === true |
|
878 | + && $this->shouldProcessElement($rule->severity) === true |
|
879 | + ) { |
|
880 | + if (isset($this->ruleset[$code]) === false) { |
|
881 | + $this->ruleset[$code] = []; |
|
882 | + } |
|
883 | + |
|
884 | + $this->ruleset[$code]['severity'] = (int) $rule->severity; |
|
885 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
886 | + echo str_repeat("\t", $depth); |
|
887 | + echo "\t\t=> severity set to ".(int) $rule->severity; |
|
888 | + if ($code !== $ref) { |
|
889 | + echo " for $code"; |
|
890 | + } |
|
891 | + |
|
892 | + echo PHP_EOL; |
|
893 | + } |
|
894 | + } |
|
895 | + |
|
896 | + // Custom message type. |
|
897 | + if (isset($rule->type) === true |
|
898 | + && $this->shouldProcessElement($rule->type) === true |
|
899 | + ) { |
|
900 | + if (isset($this->ruleset[$code]) === false) { |
|
901 | + $this->ruleset[$code] = []; |
|
902 | + } |
|
903 | + |
|
904 | + $type = strtolower((string) $rule->type); |
|
905 | + if ($type !== 'error' && $type !== 'warning') { |
|
906 | + throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\""); |
|
907 | + } |
|
908 | + |
|
909 | + $this->ruleset[$code]['type'] = $type; |
|
910 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
911 | + echo str_repeat("\t", $depth); |
|
912 | + echo "\t\t=> message type set to ".(string) $rule->type; |
|
913 | + if ($code !== $ref) { |
|
914 | + echo " for $code"; |
|
915 | + } |
|
916 | + |
|
917 | + echo PHP_EOL; |
|
918 | + } |
|
919 | + }//end if |
|
920 | + |
|
921 | + // Custom message. |
|
922 | + if (isset($rule->message) === true |
|
923 | + && $this->shouldProcessElement($rule->message) === true |
|
924 | + ) { |
|
925 | + if (isset($this->ruleset[$code]) === false) { |
|
926 | + $this->ruleset[$code] = []; |
|
927 | + } |
|
928 | + |
|
929 | + $this->ruleset[$code]['message'] = (string) $rule->message; |
|
930 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
931 | + echo str_repeat("\t", $depth); |
|
932 | + echo "\t\t=> message set to ".(string) $rule->message; |
|
933 | + if ($code !== $ref) { |
|
934 | + echo " for $code"; |
|
935 | + } |
|
936 | + |
|
937 | + echo PHP_EOL; |
|
938 | + } |
|
939 | + } |
|
940 | + |
|
941 | + // Custom properties. |
|
942 | + if (isset($rule->properties) === true |
|
943 | + && $this->shouldProcessElement($rule->properties) === true |
|
944 | + ) { |
|
945 | + foreach ($rule->properties->property as $prop) { |
|
946 | + if ($this->shouldProcessElement($prop) === false) { |
|
947 | + continue; |
|
948 | + } |
|
949 | + |
|
950 | + if (isset($this->ruleset[$code]) === false) { |
|
951 | + $this->ruleset[$code] = [ |
|
952 | + 'properties' => [], |
|
953 | + ]; |
|
954 | + } else if (isset($this->ruleset[$code]['properties']) === false) { |
|
955 | + $this->ruleset[$code]['properties'] = []; |
|
956 | + } |
|
957 | + |
|
958 | + $name = (string) $prop['name']; |
|
959 | + if (isset($prop['type']) === true |
|
960 | + && (string) $prop['type'] === 'array' |
|
961 | + ) { |
|
962 | + $values = []; |
|
963 | + if (isset($prop['extend']) === true |
|
964 | + && (string) $prop['extend'] === 'true' |
|
965 | + && isset($this->ruleset[$code]['properties'][$name]) === true |
|
966 | + ) { |
|
967 | + $values = $this->ruleset[$code]['properties'][$name]; |
|
968 | + } |
|
969 | + |
|
970 | + if (isset($prop->element) === true) { |
|
971 | + $printValue = ''; |
|
972 | + foreach ($prop->element as $element) { |
|
973 | + if ($this->shouldProcessElement($element) === false) { |
|
974 | + continue; |
|
975 | + } |
|
976 | + |
|
977 | + $value = (string) $element['value']; |
|
978 | + if (isset($element['key']) === true) { |
|
979 | + $key = (string) $element['key']; |
|
980 | + $values[$key] = $value; |
|
981 | + $printValue .= $key.'=>'.$value.','; |
|
982 | + } else { |
|
983 | + $values[] = $value; |
|
984 | + $printValue .= $value.','; |
|
985 | + } |
|
986 | + } |
|
987 | + |
|
988 | + $printValue = rtrim($printValue, ','); |
|
989 | + } else { |
|
990 | + $value = (string) $prop['value']; |
|
991 | + $printValue = $value; |
|
992 | + foreach (explode(',', $value) as $val) { |
|
993 | + list($k, $v) = explode('=>', $val.'=>'); |
|
994 | + if ($v !== '') { |
|
995 | + $values[trim($k)] = trim($v); |
|
996 | + } else { |
|
997 | + $values[] = trim($k); |
|
998 | + } |
|
999 | + } |
|
1000 | + }//end if |
|
1001 | + |
|
1002 | + $this->ruleset[$code]['properties'][$name] = $values; |
|
1003 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1004 | + echo str_repeat("\t", $depth); |
|
1005 | + echo "\t\t=> array property \"$name\" set to \"$printValue\""; |
|
1006 | + if ($code !== $ref) { |
|
1007 | + echo " for $code"; |
|
1008 | + } |
|
1009 | + |
|
1010 | + echo PHP_EOL; |
|
1011 | + } |
|
1012 | + } else { |
|
1013 | + $this->ruleset[$code]['properties'][$name] = (string) $prop['value']; |
|
1014 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1015 | + echo str_repeat("\t", $depth); |
|
1016 | + echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"'; |
|
1017 | + if ($code !== $ref) { |
|
1018 | + echo " for $code"; |
|
1019 | + } |
|
1020 | + |
|
1021 | + echo PHP_EOL; |
|
1022 | + } |
|
1023 | + }//end if |
|
1024 | + }//end foreach |
|
1025 | + }//end if |
|
1026 | + |
|
1027 | + // Ignore patterns. |
|
1028 | + foreach ($rule->{'exclude-pattern'} as $pattern) { |
|
1029 | + if ($this->shouldProcessElement($pattern) === false) { |
|
1030 | + continue; |
|
1031 | + } |
|
1032 | + |
|
1033 | + if (isset($this->ignorePatterns[$code]) === false) { |
|
1034 | + $this->ignorePatterns[$code] = []; |
|
1035 | + } |
|
1036 | + |
|
1037 | + if (isset($pattern['type']) === false) { |
|
1038 | + $pattern['type'] = 'absolute'; |
|
1039 | + } |
|
1040 | + |
|
1041 | + $this->ignorePatterns[$code][(string) $pattern] = (string) $pattern['type']; |
|
1042 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1043 | + echo str_repeat("\t", $depth); |
|
1044 | + echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern'; |
|
1045 | + if ($code !== $ref) { |
|
1046 | + echo " for $code"; |
|
1047 | + } |
|
1048 | + |
|
1049 | + echo ': '.(string) $pattern.PHP_EOL; |
|
1050 | + } |
|
1051 | + }//end foreach |
|
1052 | + |
|
1053 | + // Include patterns. |
|
1054 | + foreach ($rule->{'include-pattern'} as $pattern) { |
|
1055 | + if ($this->shouldProcessElement($pattern) === false) { |
|
1056 | + continue; |
|
1057 | + } |
|
1058 | + |
|
1059 | + if (isset($this->includePatterns[$code]) === false) { |
|
1060 | + $this->includePatterns[$code] = []; |
|
1061 | + } |
|
1062 | + |
|
1063 | + if (isset($pattern['type']) === false) { |
|
1064 | + $pattern['type'] = 'absolute'; |
|
1065 | + } |
|
1066 | + |
|
1067 | + $this->includePatterns[$code][(string) $pattern] = (string) $pattern['type']; |
|
1068 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
1069 | + echo str_repeat("\t", $depth); |
|
1070 | + echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern'; |
|
1071 | + if ($code !== $ref) { |
|
1072 | + echo " for $code"; |
|
1073 | + } |
|
1074 | + |
|
1075 | + echo ': '.(string) $pattern.PHP_EOL; |
|
1076 | + } |
|
1077 | + }//end foreach |
|
1078 | + }//end foreach |
|
1079 | + |
|
1080 | + }//end processRule() |
|
1081 | + |
|
1082 | + |
|
1083 | + /** |
|
1084 | + * Determine if an element should be processed or ignored. |
|
1085 | + * |
|
1086 | + * @param \SimpleXMLElement $element An object from a ruleset XML file. |
|
1087 | + * |
|
1088 | + * @return bool |
|
1089 | + */ |
|
1090 | + private function shouldProcessElement($element) |
|
1091 | + { |
|
1092 | + if (isset($element['phpcbf-only']) === false |
|
1093 | + && isset($element['phpcs-only']) === false |
|
1094 | + ) { |
|
1095 | + // No exceptions are being made. |
|
1096 | + return true; |
|
1097 | + } |
|
1098 | + |
|
1099 | + if (PHP_CODESNIFFER_CBF === true |
|
1100 | + && isset($element['phpcbf-only']) === true |
|
1101 | + && (string) $element['phpcbf-only'] === 'true' |
|
1102 | + ) { |
|
1103 | + return true; |
|
1104 | + } |
|
1105 | + |
|
1106 | + if (PHP_CODESNIFFER_CBF === false |
|
1107 | + && isset($element['phpcs-only']) === true |
|
1108 | + && (string) $element['phpcs-only'] === 'true' |
|
1109 | + ) { |
|
1110 | + return true; |
|
1111 | + } |
|
1112 | + |
|
1113 | + return false; |
|
1114 | + |
|
1115 | + }//end shouldProcessElement() |
|
1116 | + |
|
1117 | + |
|
1118 | + /** |
|
1119 | + * Loads and stores sniffs objects used for sniffing files. |
|
1120 | + * |
|
1121 | + * @param array $files Paths to the sniff files to register. |
|
1122 | + * @param array $restrictions The sniff class names to restrict the allowed |
|
1123 | + * listeners to. |
|
1124 | + * @param array $exclusions The sniff class names to exclude from the |
|
1125 | + * listeners list. |
|
1126 | + * |
|
1127 | + * @return void |
|
1128 | + */ |
|
1129 | + public function registerSniffs($files, $restrictions, $exclusions) |
|
1130 | + { |
|
1131 | + $listeners = []; |
|
1132 | + |
|
1133 | + foreach ($files as $file) { |
|
1134 | + // Work out where the position of /StandardName/Sniffs/... is |
|
1135 | + // so we can determine what the class will be called. |
|
1136 | + $sniffPos = strrpos($file, DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR); |
|
1137 | + if ($sniffPos === false) { |
|
1138 | + continue; |
|
1139 | + } |
|
1140 | + |
|
1141 | + $slashPos = strrpos(substr($file, 0, $sniffPos), DIRECTORY_SEPARATOR); |
|
1142 | + if ($slashPos === false) { |
|
1143 | + continue; |
|
1144 | + } |
|
1145 | + |
|
1146 | + $className = Autoload::loadFile($file); |
|
1147 | + $compareName = Util\Common::cleanSniffClass($className); |
|
1148 | + |
|
1149 | + // If they have specified a list of sniffs to restrict to, check |
|
1150 | + // to see if this sniff is allowed. |
|
1151 | + if (empty($restrictions) === false |
|
1152 | + && isset($restrictions[$compareName]) === false |
|
1153 | + ) { |
|
1154 | + continue; |
|
1155 | + } |
|
1156 | + |
|
1157 | + // If they have specified a list of sniffs to exclude, check |
|
1158 | + // to see if this sniff is allowed. |
|
1159 | + if (empty($exclusions) === false |
|
1160 | + && isset($exclusions[$compareName]) === true |
|
1161 | + ) { |
|
1162 | + continue; |
|
1163 | + } |
|
1164 | + |
|
1165 | + // Skip abstract classes. |
|
1166 | + $reflection = new \ReflectionClass($className); |
|
1167 | + if ($reflection->isAbstract() === true) { |
|
1168 | + continue; |
|
1169 | + } |
|
1170 | + |
|
1171 | + $listeners[$className] = $className; |
|
1172 | + |
|
1173 | + if (PHP_CODESNIFFER_VERBOSITY > 2) { |
|
1174 | + echo "Registered $className".PHP_EOL; |
|
1175 | + } |
|
1176 | + }//end foreach |
|
1177 | + |
|
1178 | + $this->sniffs = $listeners; |
|
1179 | + |
|
1180 | + }//end registerSniffs() |
|
1181 | + |
|
1182 | + |
|
1183 | + /** |
|
1184 | + * Populates the array of PHP_CodeSniffer_Sniff's for this file. |
|
1185 | + * |
|
1186 | + * @return void |
|
1187 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If sniff registration fails. |
|
1188 | + */ |
|
1189 | + public function populateTokenListeners() |
|
1190 | + { |
|
1191 | + // Construct a list of listeners indexed by token being listened for. |
|
1192 | + $this->tokenListeners = []; |
|
1193 | + |
|
1194 | + foreach ($this->sniffs as $sniffClass => $sniffObject) { |
|
1195 | + $this->sniffs[$sniffClass] = null; |
|
1196 | + $this->sniffs[$sniffClass] = new $sniffClass(); |
|
1197 | + |
|
1198 | + $sniffCode = Util\Common::getSniffCode($sniffClass); |
|
1199 | + $this->sniffCodes[$sniffCode] = $sniffClass; |
|
1200 | + |
|
1201 | + // Set custom properties. |
|
1202 | + if (isset($this->ruleset[$sniffCode]['properties']) === true) { |
|
1203 | + foreach ($this->ruleset[$sniffCode]['properties'] as $name => $value) { |
|
1204 | + $this->setSniffProperty($sniffClass, $name, $value); |
|
1205 | + } |
|
1206 | + } |
|
1207 | + |
|
1208 | + $tokenizers = []; |
|
1209 | + $vars = get_class_vars($sniffClass); |
|
1210 | + if (isset($vars['supportedTokenizers']) === true) { |
|
1211 | + foreach ($vars['supportedTokenizers'] as $tokenizer) { |
|
1212 | + $tokenizers[$tokenizer] = $tokenizer; |
|
1213 | + } |
|
1214 | + } else { |
|
1215 | + $tokenizers = ['PHP' => 'PHP']; |
|
1216 | + } |
|
1217 | + |
|
1218 | + $tokens = $this->sniffs[$sniffClass]->register(); |
|
1219 | + if (is_array($tokens) === false) { |
|
1220 | + $msg = "Sniff $sniffClass register() method must return an array"; |
|
1221 | + throw new RuntimeException($msg); |
|
1222 | + } |
|
1223 | + |
|
1224 | + $ignorePatterns = []; |
|
1225 | + $patterns = $this->getIgnorePatterns($sniffCode); |
|
1226 | + foreach ($patterns as $pattern => $type) { |
|
1227 | + $replacements = [ |
|
1228 | + '\\,' => ',', |
|
1229 | + '*' => '.*', |
|
1230 | + ]; |
|
1231 | + |
|
1232 | + $ignorePatterns[] = strtr($pattern, $replacements); |
|
1233 | + } |
|
1234 | + |
|
1235 | + $includePatterns = []; |
|
1236 | + $patterns = $this->getIncludePatterns($sniffCode); |
|
1237 | + foreach ($patterns as $pattern => $type) { |
|
1238 | + $replacements = [ |
|
1239 | + '\\,' => ',', |
|
1240 | + '*' => '.*', |
|
1241 | + ]; |
|
1242 | + |
|
1243 | + $includePatterns[] = strtr($pattern, $replacements); |
|
1244 | + } |
|
1245 | + |
|
1246 | + foreach ($tokens as $token) { |
|
1247 | + if (isset($this->tokenListeners[$token]) === false) { |
|
1248 | + $this->tokenListeners[$token] = []; |
|
1249 | + } |
|
1250 | + |
|
1251 | + if (isset($this->tokenListeners[$token][$sniffClass]) === false) { |
|
1252 | + $this->tokenListeners[$token][$sniffClass] = [ |
|
1253 | + 'class' => $sniffClass, |
|
1254 | + 'source' => $sniffCode, |
|
1255 | + 'tokenizers' => $tokenizers, |
|
1256 | + 'ignore' => $ignorePatterns, |
|
1257 | + 'include' => $includePatterns, |
|
1258 | + ]; |
|
1259 | + } |
|
1260 | + } |
|
1261 | + }//end foreach |
|
1262 | + |
|
1263 | + }//end populateTokenListeners() |
|
1264 | + |
|
1265 | + |
|
1266 | + /** |
|
1267 | + * Set a single property for a sniff. |
|
1268 | + * |
|
1269 | + * @param string $sniffClass The class name of the sniff. |
|
1270 | + * @param string $name The name of the property to change. |
|
1271 | + * @param string $value The new value of the property. |
|
1272 | + * |
|
1273 | + * @return void |
|
1274 | + */ |
|
1275 | + public function setSniffProperty($sniffClass, $name, $value) |
|
1276 | + { |
|
1277 | + // Setting a property for a sniff we are not using. |
|
1278 | + if (isset($this->sniffs[$sniffClass]) === false) { |
|
1279 | + return; |
|
1280 | + } |
|
1281 | + |
|
1282 | + $name = trim($name); |
|
1283 | + if (is_string($value) === true) { |
|
1284 | + $value = trim($value); |
|
1285 | + } |
|
1286 | + |
|
1287 | + if ($value === '') { |
|
1288 | + $value = null; |
|
1289 | + } |
|
1290 | + |
|
1291 | + // Special case for booleans. |
|
1292 | + if ($value === 'true') { |
|
1293 | + $value = true; |
|
1294 | + } else if ($value === 'false') { |
|
1295 | + $value = false; |
|
1296 | + } else if (substr($name, -2) === '[]') { |
|
1297 | + $name = substr($name, 0, -2); |
|
1298 | + $values = []; |
|
1299 | + if ($value !== null) { |
|
1300 | + foreach (explode(',', $value) as $val) { |
|
1301 | + list($k, $v) = explode('=>', $val.'=>'); |
|
1302 | + if ($v !== '') { |
|
1303 | + $values[trim($k)] = trim($v); |
|
1304 | + } else { |
|
1305 | + $values[] = trim($k); |
|
1306 | + } |
|
1307 | + } |
|
1308 | + } |
|
1309 | + |
|
1310 | + $value = $values; |
|
1311 | + } |
|
1312 | + |
|
1313 | + $this->sniffs[$sniffClass]->$name = $value; |
|
1314 | + |
|
1315 | + }//end setSniffProperty() |
|
1316 | + |
|
1317 | + |
|
1318 | + /** |
|
1319 | + * Gets the array of ignore patterns. |
|
1320 | + * |
|
1321 | + * Optionally takes a listener to get ignore patterns specified |
|
1322 | + * for that sniff only. |
|
1323 | + * |
|
1324 | + * @param string $listener The listener to get patterns for. If NULL, all |
|
1325 | + * patterns are returned. |
|
1326 | + * |
|
1327 | + * @return array |
|
1328 | + */ |
|
1329 | + public function getIgnorePatterns($listener=null) |
|
1330 | + { |
|
1331 | + if ($listener === null) { |
|
1332 | + return $this->ignorePatterns; |
|
1333 | + } |
|
1334 | + |
|
1335 | + if (isset($this->ignorePatterns[$listener]) === true) { |
|
1336 | + return $this->ignorePatterns[$listener]; |
|
1337 | + } |
|
1338 | + |
|
1339 | + return []; |
|
1340 | + |
|
1341 | + }//end getIgnorePatterns() |
|
1342 | + |
|
1343 | + |
|
1344 | + /** |
|
1345 | + * Gets the array of include patterns. |
|
1346 | + * |
|
1347 | + * Optionally takes a listener to get include patterns specified |
|
1348 | + * for that sniff only. |
|
1349 | + * |
|
1350 | + * @param string $listener The listener to get patterns for. If NULL, all |
|
1351 | + * patterns are returned. |
|
1352 | + * |
|
1353 | + * @return array |
|
1354 | + */ |
|
1355 | + public function getIncludePatterns($listener=null) |
|
1356 | + { |
|
1357 | + if ($listener === null) { |
|
1358 | + return $this->includePatterns; |
|
1359 | + } |
|
1360 | + |
|
1361 | + if (isset($this->includePatterns[$listener]) === true) { |
|
1362 | + return $this->includePatterns[$listener]; |
|
1363 | + } |
|
1364 | + |
|
1365 | + return []; |
|
1366 | + |
|
1367 | + }//end getIncludePatterns() |
|
1368 | 1368 | |
1369 | 1369 | |
1370 | 1370 | }//end class |
@@ -18,1697 +18,1697 @@ |
||
18 | 18 | class Config |
19 | 19 | { |
20 | 20 | |
21 | - /** |
|
22 | - * The current version. |
|
23 | - * |
|
24 | - * @var string |
|
25 | - */ |
|
26 | - const VERSION = '3.4.2'; |
|
27 | - |
|
28 | - /** |
|
29 | - * Package stability; either stable, beta or alpha. |
|
30 | - * |
|
31 | - * @var string |
|
32 | - */ |
|
33 | - const STABILITY = 'stable'; |
|
34 | - |
|
35 | - /** |
|
36 | - * An array of settings that PHPCS and PHPCBF accept. |
|
37 | - * |
|
38 | - * This array is not meant to be accessed directly. Instead, use the settings |
|
39 | - * as if they are class member vars so the __get() and __set() magic methods |
|
40 | - * can be used to validate the values. For example, to set the verbosity level to |
|
41 | - * level 2, use $this->verbosity = 2; instead of accessing this property directly. |
|
42 | - * |
|
43 | - * The list of settings are: |
|
44 | - * |
|
45 | - * string[] files The files and directories to check. |
|
46 | - * string[] standards The standards being used for checking. |
|
47 | - * int verbosity How verbose the output should be. |
|
48 | - * 0: no unnecessary output |
|
49 | - * 1: basic output for files being checked |
|
50 | - * 2: ruleset and file parsing output |
|
51 | - * 3: sniff execution output |
|
52 | - * bool interactive Enable interactive checking mode. |
|
53 | - * bool parallel Check files in parallel. |
|
54 | - * bool cache Enable the use of the file cache. |
|
55 | - * bool cacheFile A file where the cache data should be written |
|
56 | - * bool colors Display colours in output. |
|
57 | - * bool explain Explain the coding standards. |
|
58 | - * bool local Process local files in directories only (no recursion). |
|
59 | - * bool showSources Show sniff source codes in report output. |
|
60 | - * bool showProgress Show basic progress information while running. |
|
61 | - * bool quiet Quiet mode; disables progress and verbose output. |
|
62 | - * bool annotations Process phpcs: annotations. |
|
63 | - * int tabWidth How many spaces each tab is worth. |
|
64 | - * string encoding The encoding of the files being checked. |
|
65 | - * string[] sniffs The sniffs that should be used for checking. |
|
66 | - * If empty, all sniffs in the supplied standards will be used. |
|
67 | - * string[] exclude The sniffs that should be excluded from checking. |
|
68 | - * If empty, all sniffs in the supplied standards will be used. |
|
69 | - * string[] ignored Regular expressions used to ignore files and folders during checking. |
|
70 | - * string reportFile A file where the report output should be written. |
|
71 | - * string generator The documentation generator to use. |
|
72 | - * string filter The filter to use for the run. |
|
73 | - * string[] bootstrap One of more files to include before the run begins. |
|
74 | - * int reportWidth The maximum number of columns that reports should use for output. |
|
75 | - * Set to "auto" for have this value changed to the width of the terminal. |
|
76 | - * int errorSeverity The minimum severity an error must have to be displayed. |
|
77 | - * int warningSeverity The minimum severity a warning must have to be displayed. |
|
78 | - * bool recordErrors Record the content of error messages as well as error counts. |
|
79 | - * string suffix A suffix to add to fixed files. |
|
80 | - * string basepath A file system location to strip from the paths of files shown in reports. |
|
81 | - * bool stdin Read content from STDIN instead of supplied files. |
|
82 | - * string stdinContent Content passed directly to PHPCS on STDIN. |
|
83 | - * string stdinPath The path to use for content passed on STDIN. |
|
84 | - * |
|
85 | - * array<string, string> extensions File extensions that should be checked, and what tokenizer to use. |
|
86 | - * E.g., array('inc' => 'PHP'); |
|
87 | - * array<string, string|null> reports The reports to use for printing output after the run. |
|
88 | - * The format of the array is: |
|
89 | - * array( |
|
90 | - * 'reportName1' => 'outputFile', |
|
91 | - * 'reportName2' => null, |
|
92 | - * ); |
|
93 | - * If the array value is NULL, the report will be written to the screen. |
|
94 | - * |
|
95 | - * string[] unknown Any arguments gathered on the command line that are unknown to us. |
|
96 | - * E.g., using `phpcs -c` will give array('c'); |
|
97 | - * |
|
98 | - * @var array<string, mixed> |
|
99 | - */ |
|
100 | - private $settings = [ |
|
101 | - 'files' => null, |
|
102 | - 'standards' => null, |
|
103 | - 'verbosity' => null, |
|
104 | - 'interactive' => null, |
|
105 | - 'parallel' => null, |
|
106 | - 'cache' => null, |
|
107 | - 'cacheFile' => null, |
|
108 | - 'colors' => null, |
|
109 | - 'explain' => null, |
|
110 | - 'local' => null, |
|
111 | - 'showSources' => null, |
|
112 | - 'showProgress' => null, |
|
113 | - 'quiet' => null, |
|
114 | - 'annotations' => null, |
|
115 | - 'tabWidth' => null, |
|
116 | - 'encoding' => null, |
|
117 | - 'extensions' => null, |
|
118 | - 'sniffs' => null, |
|
119 | - 'exclude' => null, |
|
120 | - 'ignored' => null, |
|
121 | - 'reportFile' => null, |
|
122 | - 'generator' => null, |
|
123 | - 'filter' => null, |
|
124 | - 'bootstrap' => null, |
|
125 | - 'reports' => null, |
|
126 | - 'basepath' => null, |
|
127 | - 'reportWidth' => null, |
|
128 | - 'errorSeverity' => null, |
|
129 | - 'warningSeverity' => null, |
|
130 | - 'recordErrors' => null, |
|
131 | - 'suffix' => null, |
|
132 | - 'stdin' => null, |
|
133 | - 'stdinContent' => null, |
|
134 | - 'stdinPath' => null, |
|
135 | - 'unknown' => null, |
|
136 | - ]; |
|
137 | - |
|
138 | - /** |
|
139 | - * Whether or not to kill the process when an unknown command line arg is found. |
|
140 | - * |
|
141 | - * If FALSE, arguments that are not command line options or file/directory paths |
|
142 | - * will be ignored and execution will continue. These values will be stored in |
|
143 | - * $this->unknown. |
|
144 | - * |
|
145 | - * @var boolean |
|
146 | - */ |
|
147 | - public $dieOnUnknownArg; |
|
148 | - |
|
149 | - /** |
|
150 | - * The current command line arguments we are processing. |
|
151 | - * |
|
152 | - * @var string[] |
|
153 | - */ |
|
154 | - private $cliArgs = []; |
|
155 | - |
|
156 | - /** |
|
157 | - * Command line values that the user has supplied directly. |
|
158 | - * |
|
159 | - * @var array<string, TRUE> |
|
160 | - */ |
|
161 | - private static $overriddenDefaults = []; |
|
162 | - |
|
163 | - /** |
|
164 | - * Config file data that has been loaded for the run. |
|
165 | - * |
|
166 | - * @var array<string, string> |
|
167 | - */ |
|
168 | - private static $configData = null; |
|
169 | - |
|
170 | - /** |
|
171 | - * The full path to the config data file that has been loaded. |
|
172 | - * |
|
173 | - * @var string |
|
174 | - */ |
|
175 | - private static $configDataFile = null; |
|
176 | - |
|
177 | - /** |
|
178 | - * Automatically discovered executable utility paths. |
|
179 | - * |
|
180 | - * @var array<string, string> |
|
181 | - */ |
|
182 | - private static $executablePaths = []; |
|
183 | - |
|
184 | - |
|
185 | - /** |
|
186 | - * Get the value of an inaccessible property. |
|
187 | - * |
|
188 | - * @param string $name The name of the property. |
|
189 | - * |
|
190 | - * @return mixed |
|
191 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. |
|
192 | - */ |
|
193 | - public function __get($name) |
|
194 | - { |
|
195 | - if (array_key_exists($name, $this->settings) === false) { |
|
196 | - throw new RuntimeException("ERROR: unable to get value of property \"$name\""); |
|
197 | - } |
|
198 | - |
|
199 | - return $this->settings[$name]; |
|
200 | - |
|
201 | - }//end __get() |
|
202 | - |
|
203 | - |
|
204 | - /** |
|
205 | - * Set the value of an inaccessible property. |
|
206 | - * |
|
207 | - * @param string $name The name of the property. |
|
208 | - * @param mixed $value The value of the property. |
|
209 | - * |
|
210 | - * @return void |
|
211 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. |
|
212 | - */ |
|
213 | - public function __set($name, $value) |
|
214 | - { |
|
215 | - if (array_key_exists($name, $this->settings) === false) { |
|
216 | - throw new RuntimeException("Can't __set() $name; setting doesn't exist"); |
|
217 | - } |
|
218 | - |
|
219 | - switch ($name) { |
|
220 | - case 'reportWidth' : |
|
221 | - // Support auto terminal width. |
|
222 | - if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) { |
|
223 | - $value = (int) $matches[1]; |
|
224 | - } else { |
|
225 | - $value = (int) $value; |
|
226 | - } |
|
227 | - break; |
|
228 | - case 'standards' : |
|
229 | - $cleaned = []; |
|
230 | - |
|
231 | - // Check if the standard name is valid, or if the case is invalid. |
|
232 | - $installedStandards = Util\Standards::getInstalledStandards(); |
|
233 | - foreach ($value as $standard) { |
|
234 | - foreach ($installedStandards as $validStandard) { |
|
235 | - if (strtolower($standard) === strtolower($validStandard)) { |
|
236 | - $standard = $validStandard; |
|
237 | - break; |
|
238 | - } |
|
239 | - } |
|
240 | - |
|
241 | - $cleaned[] = $standard; |
|
242 | - } |
|
243 | - |
|
244 | - $value = $cleaned; |
|
245 | - break; |
|
246 | - default : |
|
247 | - // No validation required. |
|
248 | - break; |
|
249 | - }//end switch |
|
250 | - |
|
251 | - $this->settings[$name] = $value; |
|
252 | - |
|
253 | - }//end __set() |
|
254 | - |
|
255 | - |
|
256 | - /** |
|
257 | - * Check if the value of an inaccessible property is set. |
|
258 | - * |
|
259 | - * @param string $name The name of the property. |
|
260 | - * |
|
261 | - * @return bool |
|
262 | - */ |
|
263 | - public function __isset($name) |
|
264 | - { |
|
265 | - return isset($this->settings[$name]); |
|
266 | - |
|
267 | - }//end __isset() |
|
268 | - |
|
269 | - |
|
270 | - /** |
|
271 | - * Unset the value of an inaccessible property. |
|
272 | - * |
|
273 | - * @param string $name The name of the property. |
|
274 | - * |
|
275 | - * @return void |
|
276 | - */ |
|
277 | - public function __unset($name) |
|
278 | - { |
|
279 | - $this->settings[$name] = null; |
|
280 | - |
|
281 | - }//end __unset() |
|
282 | - |
|
283 | - |
|
284 | - /** |
|
285 | - * Get the array of all config settings. |
|
286 | - * |
|
287 | - * @return array<string, mixed> |
|
288 | - */ |
|
289 | - public function getSettings() |
|
290 | - { |
|
291 | - return $this->settings; |
|
292 | - |
|
293 | - }//end getSettings() |
|
294 | - |
|
295 | - |
|
296 | - /** |
|
297 | - * Set the array of all config settings. |
|
298 | - * |
|
299 | - * @param array<string, mixed> $settings The array of config settings. |
|
300 | - * |
|
301 | - * @return void |
|
302 | - */ |
|
303 | - public function setSettings($settings) |
|
304 | - { |
|
305 | - return $this->settings = $settings; |
|
306 | - |
|
307 | - }//end setSettings() |
|
308 | - |
|
309 | - |
|
310 | - /** |
|
311 | - * Creates a Config object and populates it with command line values. |
|
312 | - * |
|
313 | - * @param array $cliArgs An array of values gathered from CLI args. |
|
314 | - * @param bool $dieOnUnknownArg Whether or not to kill the process when an |
|
315 | - * unknown command line arg is found. |
|
316 | - * |
|
317 | - * @return void |
|
318 | - */ |
|
319 | - public function __construct(array $cliArgs=[], $dieOnUnknownArg=true) |
|
320 | - { |
|
321 | - if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { |
|
322 | - // Let everything through during testing so that we can |
|
323 | - // make use of PHPUnit command line arguments as well. |
|
324 | - $this->dieOnUnknownArg = false; |
|
325 | - } else { |
|
326 | - $this->dieOnUnknownArg = $dieOnUnknownArg; |
|
327 | - } |
|
328 | - |
|
329 | - if (empty($cliArgs) === true) { |
|
330 | - $cliArgs = $_SERVER['argv']; |
|
331 | - array_shift($cliArgs); |
|
332 | - } |
|
333 | - |
|
334 | - $this->restoreDefaults(); |
|
335 | - $this->setCommandLineValues($cliArgs); |
|
336 | - |
|
337 | - if (isset(self::$overriddenDefaults['standards']) === false) { |
|
338 | - // They did not supply a standard to use. |
|
339 | - // Look for a default ruleset in the current directory or higher. |
|
340 | - $currentDir = getcwd(); |
|
341 | - |
|
342 | - $defaultFiles = [ |
|
343 | - '.phpcs.xml', |
|
344 | - 'phpcs.xml', |
|
345 | - '.phpcs.xml.dist', |
|
346 | - 'phpcs.xml.dist', |
|
347 | - ]; |
|
348 | - |
|
349 | - do { |
|
350 | - foreach ($defaultFiles as $defaultFilename) { |
|
351 | - $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename; |
|
352 | - if (is_file($default) === true) { |
|
353 | - $this->standards = [$default]; |
|
354 | - break(2); |
|
355 | - } |
|
356 | - } |
|
357 | - |
|
358 | - $lastDir = $currentDir; |
|
359 | - $currentDir = dirname($currentDir); |
|
360 | - } while ($currentDir !== '.' && $currentDir !== $lastDir); |
|
361 | - }//end if |
|
362 | - |
|
363 | - if (defined('STDIN') === false |
|
364 | - || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' |
|
365 | - ) { |
|
366 | - return; |
|
367 | - } |
|
368 | - |
|
369 | - $handle = fopen('php://stdin', 'r'); |
|
370 | - |
|
371 | - // Check for content on STDIN. |
|
372 | - if ($this->stdin === true |
|
373 | - || (Util\Common::isStdinATTY() === false |
|
374 | - && feof($handle) === false) |
|
375 | - ) { |
|
376 | - $readStreams = [$handle]; |
|
377 | - $writeSteams = null; |
|
378 | - |
|
379 | - $fileContents = ''; |
|
380 | - while (is_resource($handle) === true && feof($handle) === false) { |
|
381 | - // Set a timeout of 200ms. |
|
382 | - if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) { |
|
383 | - break; |
|
384 | - } |
|
385 | - |
|
386 | - $fileContents .= fgets($handle); |
|
387 | - } |
|
388 | - |
|
389 | - if (trim($fileContents) !== '') { |
|
390 | - $this->stdin = true; |
|
391 | - $this->stdinContent = $fileContents; |
|
392 | - self::$overriddenDefaults['stdin'] = true; |
|
393 | - self::$overriddenDefaults['stdinContent'] = true; |
|
394 | - } |
|
395 | - }//end if |
|
396 | - |
|
397 | - fclose($handle); |
|
398 | - |
|
399 | - }//end __construct() |
|
400 | - |
|
401 | - |
|
402 | - /** |
|
403 | - * Set the command line values. |
|
404 | - * |
|
405 | - * @param array $args An array of command line arguments to set. |
|
406 | - * |
|
407 | - * @return void |
|
408 | - */ |
|
409 | - public function setCommandLineValues($args) |
|
410 | - { |
|
411 | - $this->cliArgs = $args; |
|
412 | - $numArgs = count($args); |
|
413 | - |
|
414 | - for ($i = 0; $i < $numArgs; $i++) { |
|
415 | - $arg = $this->cliArgs[$i]; |
|
416 | - if ($arg === '') { |
|
417 | - continue; |
|
418 | - } |
|
419 | - |
|
420 | - if ($arg{0} === '-') { |
|
421 | - if ($arg === '-') { |
|
422 | - // Asking to read from STDIN. |
|
423 | - $this->stdin = true; |
|
424 | - self::$overriddenDefaults['stdin'] = true; |
|
425 | - continue; |
|
426 | - } |
|
427 | - |
|
428 | - if ($arg === '--') { |
|
429 | - // Empty argument, ignore it. |
|
430 | - continue; |
|
431 | - } |
|
432 | - |
|
433 | - if ($arg{1} === '-') { |
|
434 | - $this->processLongArgument(substr($arg, 2), $i); |
|
435 | - } else { |
|
436 | - $switches = str_split($arg); |
|
437 | - foreach ($switches as $switch) { |
|
438 | - if ($switch === '-') { |
|
439 | - continue; |
|
440 | - } |
|
441 | - |
|
442 | - $this->processShortArgument($switch, $i); |
|
443 | - } |
|
444 | - } |
|
445 | - } else { |
|
446 | - $this->processUnknownArgument($arg, $i); |
|
447 | - }//end if |
|
448 | - }//end for |
|
449 | - |
|
450 | - }//end setCommandLineValues() |
|
451 | - |
|
452 | - |
|
453 | - /** |
|
454 | - * Restore default values for all possible command line arguments. |
|
455 | - * |
|
456 | - * @return array |
|
457 | - */ |
|
458 | - public function restoreDefaults() |
|
459 | - { |
|
460 | - $this->files = []; |
|
461 | - $this->standards = ['PEAR']; |
|
462 | - $this->verbosity = 0; |
|
463 | - $this->interactive = false; |
|
464 | - $this->cache = false; |
|
465 | - $this->cacheFile = null; |
|
466 | - $this->colors = false; |
|
467 | - $this->explain = false; |
|
468 | - $this->local = false; |
|
469 | - $this->showSources = false; |
|
470 | - $this->showProgress = false; |
|
471 | - $this->quiet = false; |
|
472 | - $this->annotations = true; |
|
473 | - $this->parallel = 1; |
|
474 | - $this->tabWidth = 0; |
|
475 | - $this->encoding = 'utf-8'; |
|
476 | - $this->extensions = [ |
|
477 | - 'php' => 'PHP', |
|
478 | - 'inc' => 'PHP', |
|
479 | - 'js' => 'JS', |
|
480 | - 'css' => 'CSS', |
|
481 | - ]; |
|
482 | - $this->sniffs = []; |
|
483 | - $this->exclude = []; |
|
484 | - $this->ignored = []; |
|
485 | - $this->reportFile = null; |
|
486 | - $this->generator = null; |
|
487 | - $this->filter = null; |
|
488 | - $this->bootstrap = []; |
|
489 | - $this->basepath = null; |
|
490 | - $this->reports = ['full' => null]; |
|
491 | - $this->reportWidth = 'auto'; |
|
492 | - $this->errorSeverity = 5; |
|
493 | - $this->warningSeverity = 5; |
|
494 | - $this->recordErrors = true; |
|
495 | - $this->suffix = ''; |
|
496 | - $this->stdin = false; |
|
497 | - $this->stdinContent = null; |
|
498 | - $this->stdinPath = null; |
|
499 | - $this->unknown = []; |
|
500 | - |
|
501 | - $standard = self::getConfigData('default_standard'); |
|
502 | - if ($standard !== null) { |
|
503 | - $this->standards = explode(',', $standard); |
|
504 | - } |
|
505 | - |
|
506 | - $reportFormat = self::getConfigData('report_format'); |
|
507 | - if ($reportFormat !== null) { |
|
508 | - $this->reports = [$reportFormat => null]; |
|
509 | - } |
|
510 | - |
|
511 | - $tabWidth = self::getConfigData('tab_width'); |
|
512 | - if ($tabWidth !== null) { |
|
513 | - $this->tabWidth = (int) $tabWidth; |
|
514 | - } |
|
515 | - |
|
516 | - $encoding = self::getConfigData('encoding'); |
|
517 | - if ($encoding !== null) { |
|
518 | - $this->encoding = strtolower($encoding); |
|
519 | - } |
|
520 | - |
|
521 | - $severity = self::getConfigData('severity'); |
|
522 | - if ($severity !== null) { |
|
523 | - $this->errorSeverity = (int) $severity; |
|
524 | - $this->warningSeverity = (int) $severity; |
|
525 | - } |
|
526 | - |
|
527 | - $severity = self::getConfigData('error_severity'); |
|
528 | - if ($severity !== null) { |
|
529 | - $this->errorSeverity = (int) $severity; |
|
530 | - } |
|
531 | - |
|
532 | - $severity = self::getConfigData('warning_severity'); |
|
533 | - if ($severity !== null) { |
|
534 | - $this->warningSeverity = (int) $severity; |
|
535 | - } |
|
536 | - |
|
537 | - $showWarnings = self::getConfigData('show_warnings'); |
|
538 | - if ($showWarnings !== null) { |
|
539 | - $showWarnings = (bool) $showWarnings; |
|
540 | - if ($showWarnings === false) { |
|
541 | - $this->warningSeverity = 0; |
|
542 | - } |
|
543 | - } |
|
544 | - |
|
545 | - $reportWidth = self::getConfigData('report_width'); |
|
546 | - if ($reportWidth !== null) { |
|
547 | - $this->reportWidth = $reportWidth; |
|
548 | - } |
|
549 | - |
|
550 | - $showProgress = self::getConfigData('show_progress'); |
|
551 | - if ($showProgress !== null) { |
|
552 | - $this->showProgress = (bool) $showProgress; |
|
553 | - } |
|
554 | - |
|
555 | - $quiet = self::getConfigData('quiet'); |
|
556 | - if ($quiet !== null) { |
|
557 | - $this->quiet = (bool) $quiet; |
|
558 | - } |
|
559 | - |
|
560 | - $colors = self::getConfigData('colors'); |
|
561 | - if ($colors !== null) { |
|
562 | - $this->colors = (bool) $colors; |
|
563 | - } |
|
564 | - |
|
565 | - if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { |
|
566 | - $cache = self::getConfigData('cache'); |
|
567 | - if ($cache !== null) { |
|
568 | - $this->cache = (bool) $cache; |
|
569 | - } |
|
570 | - |
|
571 | - $parallel = self::getConfigData('parallel'); |
|
572 | - if ($parallel !== null) { |
|
573 | - $this->parallel = max((int) $parallel, 1); |
|
574 | - } |
|
575 | - } |
|
576 | - |
|
577 | - }//end restoreDefaults() |
|
578 | - |
|
579 | - |
|
580 | - /** |
|
581 | - * Processes a short (-e) command line argument. |
|
582 | - * |
|
583 | - * @param string $arg The command line argument. |
|
584 | - * @param int $pos The position of the argument on the command line. |
|
585 | - * |
|
586 | - * @return void |
|
587 | - */ |
|
588 | - public function processShortArgument($arg, $pos) |
|
589 | - { |
|
590 | - switch ($arg) { |
|
591 | - case 'h': |
|
592 | - case '?': |
|
593 | - ob_start(); |
|
594 | - $this->printUsage(); |
|
595 | - $output = ob_get_contents(); |
|
596 | - ob_end_clean(); |
|
597 | - throw new DeepExitException($output, 0); |
|
598 | - case 'i' : |
|
599 | - ob_start(); |
|
600 | - Util\Standards::printInstalledStandards(); |
|
601 | - $output = ob_get_contents(); |
|
602 | - ob_end_clean(); |
|
603 | - throw new DeepExitException($output, 0); |
|
604 | - case 'v' : |
|
605 | - if ($this->quiet === true) { |
|
606 | - // Ignore when quiet mode is enabled. |
|
607 | - break; |
|
608 | - } |
|
609 | - |
|
610 | - $this->verbosity++; |
|
611 | - self::$overriddenDefaults['verbosity'] = true; |
|
612 | - break; |
|
613 | - case 'l' : |
|
614 | - $this->local = true; |
|
615 | - self::$overriddenDefaults['local'] = true; |
|
616 | - break; |
|
617 | - case 's' : |
|
618 | - $this->showSources = true; |
|
619 | - self::$overriddenDefaults['showSources'] = true; |
|
620 | - break; |
|
621 | - case 'a' : |
|
622 | - $this->interactive = true; |
|
623 | - self::$overriddenDefaults['interactive'] = true; |
|
624 | - break; |
|
625 | - case 'e': |
|
626 | - $this->explain = true; |
|
627 | - self::$overriddenDefaults['explain'] = true; |
|
628 | - break; |
|
629 | - case 'p' : |
|
630 | - if ($this->quiet === true) { |
|
631 | - // Ignore when quiet mode is enabled. |
|
632 | - break; |
|
633 | - } |
|
634 | - |
|
635 | - $this->showProgress = true; |
|
636 | - self::$overriddenDefaults['showProgress'] = true; |
|
637 | - break; |
|
638 | - case 'q' : |
|
639 | - // Quiet mode disables a few other settings as well. |
|
640 | - $this->quiet = true; |
|
641 | - $this->showProgress = false; |
|
642 | - $this->verbosity = 0; |
|
643 | - |
|
644 | - self::$overriddenDefaults['quiet'] = true; |
|
645 | - break; |
|
646 | - case 'm' : |
|
647 | - $this->recordErrors = false; |
|
648 | - self::$overriddenDefaults['recordErrors'] = true; |
|
649 | - break; |
|
650 | - case 'd' : |
|
651 | - $ini = explode('=', $this->cliArgs[($pos + 1)]); |
|
652 | - $this->cliArgs[($pos + 1)] = ''; |
|
653 | - if (isset($ini[1]) === true) { |
|
654 | - ini_set($ini[0], $ini[1]); |
|
655 | - } else { |
|
656 | - ini_set($ini[0], true); |
|
657 | - } |
|
658 | - break; |
|
659 | - case 'n' : |
|
660 | - if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
661 | - $this->warningSeverity = 0; |
|
662 | - self::$overriddenDefaults['warningSeverity'] = true; |
|
663 | - } |
|
664 | - break; |
|
665 | - case 'w' : |
|
666 | - if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
667 | - $this->warningSeverity = $this->errorSeverity; |
|
668 | - self::$overriddenDefaults['warningSeverity'] = true; |
|
669 | - } |
|
670 | - break; |
|
671 | - default: |
|
672 | - if ($this->dieOnUnknownArg === false) { |
|
673 | - $unknown = $this->unknown; |
|
674 | - $unknown[] = $arg; |
|
675 | - $this->unknown = $unknown; |
|
676 | - } else { |
|
677 | - $this->processUnknownArgument('-'.$arg, $pos); |
|
678 | - } |
|
679 | - }//end switch |
|
680 | - |
|
681 | - }//end processShortArgument() |
|
682 | - |
|
683 | - |
|
684 | - /** |
|
685 | - * Processes a long (--example) command line argument. |
|
686 | - * |
|
687 | - * @param string $arg The command line argument. |
|
688 | - * @param int $pos The position of the argument on the command line. |
|
689 | - * |
|
690 | - * @return void |
|
691 | - */ |
|
692 | - public function processLongArgument($arg, $pos) |
|
693 | - { |
|
694 | - switch ($arg) { |
|
695 | - case 'help': |
|
696 | - ob_start(); |
|
697 | - $this->printUsage(); |
|
698 | - $output = ob_get_contents(); |
|
699 | - ob_end_clean(); |
|
700 | - throw new DeepExitException($output, 0); |
|
701 | - case 'version': |
|
702 | - $output = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') '; |
|
703 | - $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL; |
|
704 | - throw new DeepExitException($output, 0); |
|
705 | - case 'colors': |
|
706 | - if (isset(self::$overriddenDefaults['colors']) === true) { |
|
707 | - break; |
|
708 | - } |
|
709 | - |
|
710 | - $this->colors = true; |
|
711 | - self::$overriddenDefaults['colors'] = true; |
|
712 | - break; |
|
713 | - case 'no-colors': |
|
714 | - if (isset(self::$overriddenDefaults['colors']) === true) { |
|
715 | - break; |
|
716 | - } |
|
717 | - |
|
718 | - $this->colors = false; |
|
719 | - self::$overriddenDefaults['colors'] = true; |
|
720 | - break; |
|
721 | - case 'cache': |
|
722 | - if (isset(self::$overriddenDefaults['cache']) === true) { |
|
723 | - break; |
|
724 | - } |
|
725 | - |
|
726 | - if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { |
|
727 | - $this->cache = true; |
|
728 | - self::$overriddenDefaults['cache'] = true; |
|
729 | - } |
|
730 | - break; |
|
731 | - case 'no-cache': |
|
732 | - if (isset(self::$overriddenDefaults['cache']) === true) { |
|
733 | - break; |
|
734 | - } |
|
735 | - |
|
736 | - $this->cache = false; |
|
737 | - self::$overriddenDefaults['cache'] = true; |
|
738 | - break; |
|
739 | - case 'ignore-annotations': |
|
740 | - if (isset(self::$overriddenDefaults['annotations']) === true) { |
|
741 | - break; |
|
742 | - } |
|
743 | - |
|
744 | - $this->annotations = false; |
|
745 | - self::$overriddenDefaults['annotations'] = true; |
|
746 | - break; |
|
747 | - case 'config-set': |
|
748 | - if (isset($this->cliArgs[($pos + 1)]) === false |
|
749 | - || isset($this->cliArgs[($pos + 2)]) === false |
|
750 | - ) { |
|
751 | - $error = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL; |
|
752 | - $error .= $this->printShortUsage(true); |
|
753 | - throw new DeepExitException($error, 3); |
|
754 | - } |
|
755 | - |
|
756 | - $key = $this->cliArgs[($pos + 1)]; |
|
757 | - $value = $this->cliArgs[($pos + 2)]; |
|
758 | - $current = self::getConfigData($key); |
|
759 | - |
|
760 | - try { |
|
761 | - $this->setConfigData($key, $value); |
|
762 | - } catch (\Exception $e) { |
|
763 | - throw new DeepExitException($e->getMessage().PHP_EOL, 3); |
|
764 | - } |
|
765 | - |
|
766 | - $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
767 | - |
|
768 | - if ($current === null) { |
|
769 | - $output .= "Config value \"$key\" added successfully".PHP_EOL; |
|
770 | - } else { |
|
771 | - $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL; |
|
772 | - } |
|
773 | - throw new DeepExitException($output, 0); |
|
774 | - case 'config-delete': |
|
775 | - if (isset($this->cliArgs[($pos + 1)]) === false) { |
|
776 | - $error = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL; |
|
777 | - $error .= $this->printShortUsage(true); |
|
778 | - throw new DeepExitException($error, 3); |
|
779 | - } |
|
780 | - |
|
781 | - $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
782 | - |
|
783 | - $key = $this->cliArgs[($pos + 1)]; |
|
784 | - $current = self::getConfigData($key); |
|
785 | - if ($current === null) { |
|
786 | - $output .= "Config value \"$key\" has not been set".PHP_EOL; |
|
787 | - } else { |
|
788 | - try { |
|
789 | - $this->setConfigData($key, null); |
|
790 | - } catch (\Exception $e) { |
|
791 | - throw new DeepExitException($e->getMessage().PHP_EOL, 3); |
|
792 | - } |
|
793 | - |
|
794 | - $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL; |
|
795 | - } |
|
796 | - throw new DeepExitException($output, 0); |
|
797 | - case 'config-show': |
|
798 | - ob_start(); |
|
799 | - $data = self::getAllConfigData(); |
|
800 | - echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
801 | - $this->printConfigData($data); |
|
802 | - $output = ob_get_contents(); |
|
803 | - ob_end_clean(); |
|
804 | - throw new DeepExitException($output, 0); |
|
805 | - case 'runtime-set': |
|
806 | - if (isset($this->cliArgs[($pos + 1)]) === false |
|
807 | - || isset($this->cliArgs[($pos + 2)]) === false |
|
808 | - ) { |
|
809 | - $error = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL; |
|
810 | - $error .= $this->printShortUsage(true); |
|
811 | - throw new DeepExitException($error, 3); |
|
812 | - } |
|
813 | - |
|
814 | - $key = $this->cliArgs[($pos + 1)]; |
|
815 | - $value = $this->cliArgs[($pos + 2)]; |
|
816 | - $this->cliArgs[($pos + 1)] = ''; |
|
817 | - $this->cliArgs[($pos + 2)] = ''; |
|
818 | - self::setConfigData($key, $value, true); |
|
819 | - if (isset(self::$overriddenDefaults['runtime-set']) === false) { |
|
820 | - self::$overriddenDefaults['runtime-set'] = []; |
|
821 | - } |
|
822 | - |
|
823 | - self::$overriddenDefaults['runtime-set'][$key] = true; |
|
824 | - break; |
|
825 | - default: |
|
826 | - if (substr($arg, 0, 7) === 'sniffs=') { |
|
827 | - if (isset(self::$overriddenDefaults['sniffs']) === true) { |
|
828 | - break; |
|
829 | - } |
|
830 | - |
|
831 | - $sniffs = explode(',', substr($arg, 7)); |
|
832 | - foreach ($sniffs as $sniff) { |
|
833 | - if (substr_count($sniff, '.') !== 2) { |
|
834 | - $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; |
|
835 | - $error .= $this->printShortUsage(true); |
|
836 | - throw new DeepExitException($error, 3); |
|
837 | - } |
|
838 | - } |
|
839 | - |
|
840 | - $this->sniffs = $sniffs; |
|
841 | - self::$overriddenDefaults['sniffs'] = true; |
|
842 | - } else if (substr($arg, 0, 8) === 'exclude=') { |
|
843 | - if (isset(self::$overriddenDefaults['exclude']) === true) { |
|
844 | - break; |
|
845 | - } |
|
846 | - |
|
847 | - $sniffs = explode(',', substr($arg, 8)); |
|
848 | - foreach ($sniffs as $sniff) { |
|
849 | - if (substr_count($sniff, '.') !== 2) { |
|
850 | - $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; |
|
851 | - $error .= $this->printShortUsage(true); |
|
852 | - throw new DeepExitException($error, 3); |
|
853 | - } |
|
854 | - } |
|
855 | - |
|
856 | - $this->exclude = $sniffs; |
|
857 | - self::$overriddenDefaults['exclude'] = true; |
|
858 | - } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false |
|
859 | - && substr($arg, 0, 6) === 'cache=' |
|
860 | - ) { |
|
861 | - if ((isset(self::$overriddenDefaults['cache']) === true |
|
862 | - && $this->cache === false) |
|
863 | - || isset(self::$overriddenDefaults['cacheFile']) === true |
|
864 | - ) { |
|
865 | - break; |
|
866 | - } |
|
867 | - |
|
868 | - // Turn caching on. |
|
869 | - $this->cache = true; |
|
870 | - self::$overriddenDefaults['cache'] = true; |
|
871 | - |
|
872 | - $this->cacheFile = Util\Common::realpath(substr($arg, 6)); |
|
873 | - |
|
874 | - // It may not exist and return false instead. |
|
875 | - if ($this->cacheFile === false) { |
|
876 | - $this->cacheFile = substr($arg, 6); |
|
877 | - |
|
878 | - $dir = dirname($this->cacheFile); |
|
879 | - if (is_dir($dir) === false) { |
|
880 | - $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
881 | - $error .= $this->printShortUsage(true); |
|
882 | - throw new DeepExitException($error, 3); |
|
883 | - } |
|
884 | - |
|
885 | - if ($dir === '.') { |
|
886 | - // Passed cache file is a file in the current directory. |
|
887 | - $this->cacheFile = getcwd().'/'.basename($this->cacheFile); |
|
888 | - } else { |
|
889 | - if ($dir{0} === '/') { |
|
890 | - // An absolute path. |
|
891 | - $dir = Util\Common::realpath($dir); |
|
892 | - } else { |
|
893 | - $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
894 | - } |
|
895 | - |
|
896 | - if ($dir !== false) { |
|
897 | - // Cache file path is relative. |
|
898 | - $this->cacheFile = $dir.'/'.basename($this->cacheFile); |
|
899 | - } |
|
900 | - } |
|
901 | - }//end if |
|
902 | - |
|
903 | - self::$overriddenDefaults['cacheFile'] = true; |
|
904 | - |
|
905 | - if (is_dir($this->cacheFile) === true) { |
|
906 | - $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL; |
|
907 | - $error .= $this->printShortUsage(true); |
|
908 | - throw new DeepExitException($error, 3); |
|
909 | - } |
|
910 | - } else if (substr($arg, 0, 10) === 'bootstrap=') { |
|
911 | - $files = explode(',', substr($arg, 10)); |
|
912 | - $bootstrap = []; |
|
913 | - foreach ($files as $file) { |
|
914 | - $path = Util\Common::realpath($file); |
|
915 | - if ($path === false) { |
|
916 | - $error = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL; |
|
917 | - $error .= $this->printShortUsage(true); |
|
918 | - throw new DeepExitException($error, 3); |
|
919 | - } |
|
920 | - |
|
921 | - $bootstrap[] = $path; |
|
922 | - } |
|
923 | - |
|
924 | - $this->bootstrap = array_merge($this->bootstrap, $bootstrap); |
|
925 | - self::$overriddenDefaults['bootstrap'] = true; |
|
926 | - } else if (substr($arg, 0, 10) === 'file-list=') { |
|
927 | - $fileList = substr($arg, 10); |
|
928 | - $path = Util\Common::realpath($fileList); |
|
929 | - if ($path === false) { |
|
930 | - $error = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL; |
|
931 | - $error .= $this->printShortUsage(true); |
|
932 | - throw new DeepExitException($error, 3); |
|
933 | - } |
|
934 | - |
|
935 | - $files = file($path); |
|
936 | - foreach ($files as $inputFile) { |
|
937 | - $inputFile = trim($inputFile); |
|
938 | - |
|
939 | - // Skip empty lines. |
|
940 | - if ($inputFile === '') { |
|
941 | - continue; |
|
942 | - } |
|
943 | - |
|
944 | - $this->processFilePath($inputFile); |
|
945 | - } |
|
946 | - } else if (substr($arg, 0, 11) === 'stdin-path=') { |
|
947 | - if (isset(self::$overriddenDefaults['stdinPath']) === true) { |
|
948 | - break; |
|
949 | - } |
|
950 | - |
|
951 | - $this->stdinPath = Util\Common::realpath(substr($arg, 11)); |
|
952 | - |
|
953 | - // It may not exist and return false instead, so use whatever they gave us. |
|
954 | - if ($this->stdinPath === false) { |
|
955 | - $this->stdinPath = trim(substr($arg, 11)); |
|
956 | - } |
|
957 | - |
|
958 | - self::$overriddenDefaults['stdinPath'] = true; |
|
959 | - } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') { |
|
960 | - if (isset(self::$overriddenDefaults['reportFile']) === true) { |
|
961 | - break; |
|
962 | - } |
|
963 | - |
|
964 | - $this->reportFile = Util\Common::realpath(substr($arg, 12)); |
|
965 | - |
|
966 | - // It may not exist and return false instead. |
|
967 | - if ($this->reportFile === false) { |
|
968 | - $this->reportFile = substr($arg, 12); |
|
969 | - |
|
970 | - $dir = dirname($this->reportFile); |
|
971 | - if (is_dir($dir) === false) { |
|
972 | - $error = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
973 | - $error .= $this->printShortUsage(true); |
|
974 | - throw new DeepExitException($error, 3); |
|
975 | - } |
|
976 | - |
|
977 | - if ($dir === '.') { |
|
978 | - // Passed report file is a file in the current directory. |
|
979 | - $this->reportFile = getcwd().'/'.basename($this->reportFile); |
|
980 | - } else { |
|
981 | - if ($dir{0} === '/') { |
|
982 | - // An absolute path. |
|
983 | - $dir = Util\Common::realpath($dir); |
|
984 | - } else { |
|
985 | - $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
986 | - } |
|
987 | - |
|
988 | - if ($dir !== false) { |
|
989 | - // Report file path is relative. |
|
990 | - $this->reportFile = $dir.'/'.basename($this->reportFile); |
|
991 | - } |
|
992 | - } |
|
993 | - }//end if |
|
994 | - |
|
995 | - self::$overriddenDefaults['reportFile'] = true; |
|
996 | - |
|
997 | - if (is_dir($this->reportFile) === true) { |
|
998 | - $error = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL; |
|
999 | - $error .= $this->printShortUsage(true); |
|
1000 | - throw new DeepExitException($error, 3); |
|
1001 | - } |
|
1002 | - } else if (substr($arg, 0, 13) === 'report-width=') { |
|
1003 | - if (isset(self::$overriddenDefaults['reportWidth']) === true) { |
|
1004 | - break; |
|
1005 | - } |
|
1006 | - |
|
1007 | - $this->reportWidth = substr($arg, 13); |
|
1008 | - self::$overriddenDefaults['reportWidth'] = true; |
|
1009 | - } else if (substr($arg, 0, 9) === 'basepath=') { |
|
1010 | - if (isset(self::$overriddenDefaults['basepath']) === true) { |
|
1011 | - break; |
|
1012 | - } |
|
1013 | - |
|
1014 | - self::$overriddenDefaults['basepath'] = true; |
|
1015 | - |
|
1016 | - if (substr($arg, 9) === '') { |
|
1017 | - $this->basepath = null; |
|
1018 | - break; |
|
1019 | - } |
|
1020 | - |
|
1021 | - $this->basepath = Util\Common::realpath(substr($arg, 9)); |
|
1022 | - |
|
1023 | - // It may not exist and return false instead. |
|
1024 | - if ($this->basepath === false) { |
|
1025 | - $this->basepath = substr($arg, 9); |
|
1026 | - } |
|
1027 | - |
|
1028 | - if (is_dir($this->basepath) === false) { |
|
1029 | - $error = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
1030 | - $error .= $this->printShortUsage(true); |
|
1031 | - throw new DeepExitException($error, 3); |
|
1032 | - } |
|
1033 | - } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) { |
|
1034 | - $reports = []; |
|
1035 | - |
|
1036 | - if ($arg[6] === '-') { |
|
1037 | - // This is a report with file output. |
|
1038 | - $split = strpos($arg, '='); |
|
1039 | - if ($split === false) { |
|
1040 | - $report = substr($arg, 7); |
|
1041 | - $output = null; |
|
1042 | - } else { |
|
1043 | - $report = substr($arg, 7, ($split - 7)); |
|
1044 | - $output = substr($arg, ($split + 1)); |
|
1045 | - if ($output === false) { |
|
1046 | - $output = null; |
|
1047 | - } else { |
|
1048 | - $dir = dirname($output); |
|
1049 | - if (is_dir($dir) === false) { |
|
1050 | - $error = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
1051 | - $error .= $this->printShortUsage(true); |
|
1052 | - throw new DeepExitException($error, 3); |
|
1053 | - } |
|
1054 | - |
|
1055 | - if ($dir === '.') { |
|
1056 | - // Passed report file is a filename in the current directory. |
|
1057 | - $output = getcwd().'/'.basename($output); |
|
1058 | - } else { |
|
1059 | - if ($dir{0} === '/') { |
|
1060 | - // An absolute path. |
|
1061 | - $dir = Util\Common::realpath($dir); |
|
1062 | - } else { |
|
1063 | - $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
1064 | - } |
|
1065 | - |
|
1066 | - if ($dir !== false) { |
|
1067 | - // Report file path is relative. |
|
1068 | - $output = $dir.'/'.basename($output); |
|
1069 | - } |
|
1070 | - } |
|
1071 | - }//end if |
|
1072 | - }//end if |
|
1073 | - |
|
1074 | - $reports[$report] = $output; |
|
1075 | - } else { |
|
1076 | - // This is a single report. |
|
1077 | - if (isset(self::$overriddenDefaults['reports']) === true) { |
|
1078 | - break; |
|
1079 | - } |
|
1080 | - |
|
1081 | - $reportNames = explode(',', substr($arg, 7)); |
|
1082 | - foreach ($reportNames as $report) { |
|
1083 | - $reports[$report] = null; |
|
1084 | - } |
|
1085 | - }//end if |
|
1086 | - |
|
1087 | - // Remove the default value so the CLI value overrides it. |
|
1088 | - if (isset(self::$overriddenDefaults['reports']) === false) { |
|
1089 | - $this->reports = $reports; |
|
1090 | - } else { |
|
1091 | - $this->reports = array_merge($this->reports, $reports); |
|
1092 | - } |
|
1093 | - |
|
1094 | - self::$overriddenDefaults['reports'] = true; |
|
1095 | - } else if (substr($arg, 0, 7) === 'filter=') { |
|
1096 | - if (isset(self::$overriddenDefaults['filter']) === true) { |
|
1097 | - break; |
|
1098 | - } |
|
1099 | - |
|
1100 | - $this->filter = substr($arg, 7); |
|
1101 | - self::$overriddenDefaults['filter'] = true; |
|
1102 | - } else if (substr($arg, 0, 9) === 'standard=') { |
|
1103 | - $standards = trim(substr($arg, 9)); |
|
1104 | - if ($standards !== '') { |
|
1105 | - $this->standards = explode(',', $standards); |
|
1106 | - } |
|
1107 | - |
|
1108 | - self::$overriddenDefaults['standards'] = true; |
|
1109 | - } else if (substr($arg, 0, 11) === 'extensions=') { |
|
1110 | - if (isset(self::$overriddenDefaults['extensions']) === true) { |
|
1111 | - break; |
|
1112 | - } |
|
1113 | - |
|
1114 | - $extensions = explode(',', substr($arg, 11)); |
|
1115 | - $newExtensions = []; |
|
1116 | - foreach ($extensions as $ext) { |
|
1117 | - $slash = strpos($ext, '/'); |
|
1118 | - if ($slash !== false) { |
|
1119 | - // They specified the tokenizer too. |
|
1120 | - list($ext, $tokenizer) = explode('/', $ext); |
|
1121 | - $newExtensions[$ext] = strtoupper($tokenizer); |
|
1122 | - continue; |
|
1123 | - } |
|
1124 | - |
|
1125 | - if (isset($this->extensions[$ext]) === true) { |
|
1126 | - $newExtensions[$ext] = $this->extensions[$ext]; |
|
1127 | - } else { |
|
1128 | - $newExtensions[$ext] = 'PHP'; |
|
1129 | - } |
|
1130 | - } |
|
1131 | - |
|
1132 | - $this->extensions = $newExtensions; |
|
1133 | - self::$overriddenDefaults['extensions'] = true; |
|
1134 | - } else if (substr($arg, 0, 7) === 'suffix=') { |
|
1135 | - if (isset(self::$overriddenDefaults['suffix']) === true) { |
|
1136 | - break; |
|
1137 | - } |
|
1138 | - |
|
1139 | - $this->suffix = substr($arg, 7); |
|
1140 | - self::$overriddenDefaults['suffix'] = true; |
|
1141 | - } else if (substr($arg, 0, 9) === 'parallel=') { |
|
1142 | - if (isset(self::$overriddenDefaults['parallel']) === true) { |
|
1143 | - break; |
|
1144 | - } |
|
1145 | - |
|
1146 | - $this->parallel = max((int) substr($arg, 9), 1); |
|
1147 | - self::$overriddenDefaults['parallel'] = true; |
|
1148 | - } else if (substr($arg, 0, 9) === 'severity=') { |
|
1149 | - $this->errorSeverity = (int) substr($arg, 9); |
|
1150 | - $this->warningSeverity = $this->errorSeverity; |
|
1151 | - if (isset(self::$overriddenDefaults['errorSeverity']) === false) { |
|
1152 | - self::$overriddenDefaults['errorSeverity'] = true; |
|
1153 | - } |
|
1154 | - |
|
1155 | - if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
1156 | - self::$overriddenDefaults['warningSeverity'] = true; |
|
1157 | - } |
|
1158 | - } else if (substr($arg, 0, 15) === 'error-severity=') { |
|
1159 | - if (isset(self::$overriddenDefaults['errorSeverity']) === true) { |
|
1160 | - break; |
|
1161 | - } |
|
1162 | - |
|
1163 | - $this->errorSeverity = (int) substr($arg, 15); |
|
1164 | - self::$overriddenDefaults['errorSeverity'] = true; |
|
1165 | - } else if (substr($arg, 0, 17) === 'warning-severity=') { |
|
1166 | - if (isset(self::$overriddenDefaults['warningSeverity']) === true) { |
|
1167 | - break; |
|
1168 | - } |
|
1169 | - |
|
1170 | - $this->warningSeverity = (int) substr($arg, 17); |
|
1171 | - self::$overriddenDefaults['warningSeverity'] = true; |
|
1172 | - } else if (substr($arg, 0, 7) === 'ignore=') { |
|
1173 | - if (isset(self::$overriddenDefaults['ignored']) === true) { |
|
1174 | - break; |
|
1175 | - } |
|
1176 | - |
|
1177 | - // Split the ignore string on commas, unless the comma is escaped |
|
1178 | - // using 1 or 3 slashes (\, or \\\,). |
|
1179 | - $patterns = preg_split( |
|
1180 | - '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/', |
|
1181 | - substr($arg, 7) |
|
1182 | - ); |
|
1183 | - |
|
1184 | - $ignored = []; |
|
1185 | - foreach ($patterns as $pattern) { |
|
1186 | - $pattern = trim($pattern); |
|
1187 | - if ($pattern === '') { |
|
1188 | - continue; |
|
1189 | - } |
|
1190 | - |
|
1191 | - $ignored[$pattern] = 'absolute'; |
|
1192 | - } |
|
1193 | - |
|
1194 | - $this->ignored = $ignored; |
|
1195 | - self::$overriddenDefaults['ignored'] = true; |
|
1196 | - } else if (substr($arg, 0, 10) === 'generator=' |
|
1197 | - && PHP_CODESNIFFER_CBF === false |
|
1198 | - ) { |
|
1199 | - if (isset(self::$overriddenDefaults['generator']) === true) { |
|
1200 | - break; |
|
1201 | - } |
|
1202 | - |
|
1203 | - $this->generator = substr($arg, 10); |
|
1204 | - self::$overriddenDefaults['generator'] = true; |
|
1205 | - } else if (substr($arg, 0, 9) === 'encoding=') { |
|
1206 | - if (isset(self::$overriddenDefaults['encoding']) === true) { |
|
1207 | - break; |
|
1208 | - } |
|
1209 | - |
|
1210 | - $this->encoding = strtolower(substr($arg, 9)); |
|
1211 | - self::$overriddenDefaults['encoding'] = true; |
|
1212 | - } else if (substr($arg, 0, 10) === 'tab-width=') { |
|
1213 | - if (isset(self::$overriddenDefaults['tabWidth']) === true) { |
|
1214 | - break; |
|
1215 | - } |
|
1216 | - |
|
1217 | - $this->tabWidth = (int) substr($arg, 10); |
|
1218 | - self::$overriddenDefaults['tabWidth'] = true; |
|
1219 | - } else { |
|
1220 | - if ($this->dieOnUnknownArg === false) { |
|
1221 | - $eqPos = strpos($arg, '='); |
|
1222 | - try { |
|
1223 | - if ($eqPos === false) { |
|
1224 | - $this->values[$arg] = $arg; |
|
1225 | - } else { |
|
1226 | - $value = substr($arg, ($eqPos + 1)); |
|
1227 | - $arg = substr($arg, 0, $eqPos); |
|
1228 | - $this->values[$arg] = $value; |
|
1229 | - } |
|
1230 | - } catch (RuntimeException $e) { |
|
1231 | - // Value is not valid, so just ignore it. |
|
1232 | - } |
|
1233 | - } else { |
|
1234 | - $this->processUnknownArgument('--'.$arg, $pos); |
|
1235 | - } |
|
1236 | - }//end if |
|
1237 | - break; |
|
1238 | - }//end switch |
|
1239 | - |
|
1240 | - }//end processLongArgument() |
|
1241 | - |
|
1242 | - |
|
1243 | - /** |
|
1244 | - * Processes an unknown command line argument. |
|
1245 | - * |
|
1246 | - * Assumes all unknown arguments are files and folders to check. |
|
1247 | - * |
|
1248 | - * @param string $arg The command line argument. |
|
1249 | - * @param int $pos The position of the argument on the command line. |
|
1250 | - * |
|
1251 | - * @return void |
|
1252 | - */ |
|
1253 | - public function processUnknownArgument($arg, $pos) |
|
1254 | - { |
|
1255 | - // We don't know about any additional switches; just files. |
|
1256 | - if ($arg{0} === '-') { |
|
1257 | - if ($this->dieOnUnknownArg === false) { |
|
1258 | - return; |
|
1259 | - } |
|
1260 | - |
|
1261 | - $error = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL; |
|
1262 | - $error .= $this->printShortUsage(true); |
|
1263 | - throw new DeepExitException($error, 3); |
|
1264 | - } |
|
1265 | - |
|
1266 | - $this->processFilePath($arg); |
|
1267 | - |
|
1268 | - }//end processUnknownArgument() |
|
1269 | - |
|
1270 | - |
|
1271 | - /** |
|
1272 | - * Processes a file path and add it to the file list. |
|
1273 | - * |
|
1274 | - * @param string $path The path to the file to add. |
|
1275 | - * |
|
1276 | - * @return void |
|
1277 | - */ |
|
1278 | - public function processFilePath($path) |
|
1279 | - { |
|
1280 | - // If we are processing STDIN, don't record any files to check. |
|
1281 | - if ($this->stdin === true) { |
|
1282 | - return; |
|
1283 | - } |
|
1284 | - |
|
1285 | - $file = Util\Common::realpath($path); |
|
1286 | - if (file_exists($file) === false) { |
|
1287 | - if ($this->dieOnUnknownArg === false) { |
|
1288 | - return; |
|
1289 | - } |
|
1290 | - |
|
1291 | - $error = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL; |
|
1292 | - $error .= $this->printShortUsage(true); |
|
1293 | - throw new DeepExitException($error, 3); |
|
1294 | - } else { |
|
1295 | - // Can't modify the files array directly because it's not a real |
|
1296 | - // class member, so need to use this little get/modify/set trick. |
|
1297 | - $files = $this->files; |
|
1298 | - $files[] = $file; |
|
1299 | - $this->files = $files; |
|
1300 | - self::$overriddenDefaults['files'] = true; |
|
1301 | - } |
|
1302 | - |
|
1303 | - }//end processFilePath() |
|
1304 | - |
|
1305 | - |
|
1306 | - /** |
|
1307 | - * Prints out the usage information for this script. |
|
1308 | - * |
|
1309 | - * @return void |
|
1310 | - */ |
|
1311 | - public function printUsage() |
|
1312 | - { |
|
1313 | - echo PHP_EOL; |
|
1314 | - |
|
1315 | - if (PHP_CODESNIFFER_CBF === true) { |
|
1316 | - $this->printPHPCBFUsage(); |
|
1317 | - } else { |
|
1318 | - $this->printPHPCSUsage(); |
|
1319 | - } |
|
1320 | - |
|
1321 | - echo PHP_EOL; |
|
1322 | - |
|
1323 | - }//end printUsage() |
|
1324 | - |
|
1325 | - |
|
1326 | - /** |
|
1327 | - * Prints out the short usage information for this script. |
|
1328 | - * |
|
1329 | - * @param bool $return If TRUE, the usage string is returned |
|
1330 | - * instead of output to screen. |
|
1331 | - * |
|
1332 | - * @return string|void |
|
1333 | - */ |
|
1334 | - public function printShortUsage($return=false) |
|
1335 | - { |
|
1336 | - if (PHP_CODESNIFFER_CBF === true) { |
|
1337 | - $usage = 'Run "phpcbf --help" for usage information'; |
|
1338 | - } else { |
|
1339 | - $usage = 'Run "phpcs --help" for usage information'; |
|
1340 | - } |
|
1341 | - |
|
1342 | - $usage .= PHP_EOL.PHP_EOL; |
|
1343 | - |
|
1344 | - if ($return === true) { |
|
1345 | - return $usage; |
|
1346 | - } |
|
1347 | - |
|
1348 | - echo $usage; |
|
1349 | - |
|
1350 | - }//end printShortUsage() |
|
1351 | - |
|
1352 | - |
|
1353 | - /** |
|
1354 | - * Prints out the usage information for PHPCS. |
|
1355 | - * |
|
1356 | - * @return void |
|
1357 | - */ |
|
1358 | - public function printPHPCSUsage() |
|
1359 | - { |
|
1360 | - echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL; |
|
1361 | - echo ' [--cache[=<cacheFile>]] [--no-cache] [--tab-width=<tabWidth>]'.PHP_EOL; |
|
1362 | - echo ' [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>]'.PHP_EOL; |
|
1363 | - echo ' [--report-width=<reportWidth>] [--basepath=<basepath>] [--bootstrap=<bootstrap>]'.PHP_EOL; |
|
1364 | - echo ' [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL; |
|
1365 | - echo ' [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL; |
|
1366 | - echo ' [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>]'.PHP_EOL; |
|
1367 | - echo ' [--encoding=<encoding>] [--parallel=<processes>] [--generator=<generator>]'.PHP_EOL; |
|
1368 | - echo ' [--extensions=<extensions>] [--ignore=<patterns>] [--ignore-annotations]'.PHP_EOL; |
|
1369 | - echo ' [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL; |
|
1370 | - echo PHP_EOL; |
|
1371 | - echo ' - Check STDIN instead of local files and directories'.PHP_EOL; |
|
1372 | - echo ' -n Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL; |
|
1373 | - echo ' -w Print both warnings and errors (this is the default)'.PHP_EOL; |
|
1374 | - echo ' -l Local directory only, no recursion'.PHP_EOL; |
|
1375 | - echo ' -s Show sniff codes in all reports'.PHP_EOL; |
|
1376 | - echo ' -a Run interactively'.PHP_EOL; |
|
1377 | - echo ' -e Explain a standard by showing the sniffs it includes'.PHP_EOL; |
|
1378 | - echo ' -p Show progress of the run'.PHP_EOL; |
|
1379 | - echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; |
|
1380 | - echo ' -m Stop error messages from being recorded'.PHP_EOL; |
|
1381 | - echo ' (saves a lot of memory, but stops many reports from being used)'.PHP_EOL; |
|
1382 | - echo ' -v Print processed files'.PHP_EOL; |
|
1383 | - echo ' -vv Print ruleset and token output'.PHP_EOL; |
|
1384 | - echo ' -vvv Print sniff processing information'.PHP_EOL; |
|
1385 | - echo ' -i Show a list of installed coding standards'.PHP_EOL; |
|
1386 | - echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; |
|
1387 | - echo PHP_EOL; |
|
1388 | - echo ' --help Print this help message'.PHP_EOL; |
|
1389 | - echo ' --version Print version information'.PHP_EOL; |
|
1390 | - echo ' --colors Use colors in output'.PHP_EOL; |
|
1391 | - echo ' --no-colors Do not use colors in output (this is the default)'.PHP_EOL; |
|
1392 | - echo ' --cache Cache results between runs'.PHP_EOL; |
|
1393 | - echo ' --no-cache Do not cache results between runs (this is the default)'.PHP_EOL; |
|
1394 | - echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; |
|
1395 | - echo PHP_EOL; |
|
1396 | - echo ' <cacheFile> Use a specific file for caching (uses a temporary file by default)'.PHP_EOL; |
|
1397 | - echo ' <basepath> A path to strip from the front of file paths inside reports'.PHP_EOL; |
|
1398 | - echo ' <bootstrap> A comma separated list of files to run before processing begins'.PHP_EOL; |
|
1399 | - echo ' <encoding> The encoding of the files being checked (default is utf-8)'.PHP_EOL; |
|
1400 | - echo ' <extensions> A comma separated list of file extensions to check'.PHP_EOL; |
|
1401 | - echo ' The type of the file can be specified using: ext/type'.PHP_EOL; |
|
1402 | - echo ' e.g., module/php,es/js'.PHP_EOL; |
|
1403 | - echo ' <file> One or more files and/or directories to check'.PHP_EOL; |
|
1404 | - echo ' <fileList> A file containing a list of files and/or directories to check (one per line)'.PHP_EOL; |
|
1405 | - echo ' <filter> Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; |
|
1406 | - echo ' <generator> Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL; |
|
1407 | - echo ' (forces documentation generation instead of checking)'.PHP_EOL; |
|
1408 | - echo ' <patterns> A comma separated list of patterns to ignore files and directories'.PHP_EOL; |
|
1409 | - echo ' <processes> How many files should be checked simultaneously (default is 1)'.PHP_EOL; |
|
1410 | - echo ' <report> Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL; |
|
1411 | - echo ' "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL; |
|
1412 | - echo ' "svnblame", "gitblame", "hgblame" or "notifysend" report,'.PHP_EOL; |
|
1413 | - echo ' or specify the path to a custom report class'.PHP_EOL; |
|
1414 | - echo ' (the "full" report is printed by default)'.PHP_EOL; |
|
1415 | - echo ' <reportFile> Write the report to the specified file path'.PHP_EOL; |
|
1416 | - echo ' <reportWidth> How many columns wide screen reports should be printed'.PHP_EOL; |
|
1417 | - echo ' or set to "auto" to use current screen width, where supported'.PHP_EOL; |
|
1418 | - echo ' <severity> The minimum severity required to display an error or warning'.PHP_EOL; |
|
1419 | - echo ' <sniffs> A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL; |
|
1420 | - echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; |
|
1421 | - echo ' <standard> The name or path of the coding standard to use'.PHP_EOL; |
|
1422 | - echo ' <stdinPath> If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; |
|
1423 | - echo ' <tabWidth> The number of spaces each tab represents'.PHP_EOL; |
|
1424 | - |
|
1425 | - }//end printPHPCSUsage() |
|
1426 | - |
|
1427 | - |
|
1428 | - /** |
|
1429 | - * Prints out the usage information for PHPCBF. |
|
1430 | - * |
|
1431 | - * @return void |
|
1432 | - */ |
|
1433 | - public function printPHPCBFUsage() |
|
1434 | - { |
|
1435 | - echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=<bootstrap>]'.PHP_EOL; |
|
1436 | - echo ' [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL; |
|
1437 | - echo ' [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL; |
|
1438 | - echo ' [--tab-width=<tabWidth>] [--encoding=<encoding>] [--parallel=<processes>]'.PHP_EOL; |
|
1439 | - echo ' [--basepath=<basepath>] [--extensions=<extensions>] [--ignore=<patterns>]'.PHP_EOL; |
|
1440 | - echo ' [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL; |
|
1441 | - echo PHP_EOL; |
|
1442 | - echo ' - Fix STDIN instead of local files and directories'.PHP_EOL; |
|
1443 | - echo ' -n Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL; |
|
1444 | - echo ' -w Fix both warnings and errors (on by default)'.PHP_EOL; |
|
1445 | - echo ' -l Local directory only, no recursion'.PHP_EOL; |
|
1446 | - echo ' -p Show progress of the run'.PHP_EOL; |
|
1447 | - echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; |
|
1448 | - echo ' -v Print processed files'.PHP_EOL; |
|
1449 | - echo ' -vv Print ruleset and token output'.PHP_EOL; |
|
1450 | - echo ' -vvv Print sniff processing information'.PHP_EOL; |
|
1451 | - echo ' -i Show a list of installed coding standards'.PHP_EOL; |
|
1452 | - echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; |
|
1453 | - echo PHP_EOL; |
|
1454 | - echo ' --help Print this help message'.PHP_EOL; |
|
1455 | - echo ' --version Print version information'.PHP_EOL; |
|
1456 | - echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; |
|
1457 | - echo PHP_EOL; |
|
1458 | - echo ' <basepath> A path to strip from the front of file paths inside reports'.PHP_EOL; |
|
1459 | - echo ' <bootstrap> A comma separated list of files to run before processing begins'.PHP_EOL; |
|
1460 | - echo ' <encoding> The encoding of the files being fixed (default is utf-8)'.PHP_EOL; |
|
1461 | - echo ' <extensions> A comma separated list of file extensions to fix'.PHP_EOL; |
|
1462 | - echo ' The type of the file can be specified using: ext/type'.PHP_EOL; |
|
1463 | - echo ' e.g., module/php,es/js'.PHP_EOL; |
|
1464 | - echo ' <file> One or more files and/or directories to fix'.PHP_EOL; |
|
1465 | - echo ' <fileList> A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL; |
|
1466 | - echo ' <filter> Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; |
|
1467 | - echo ' <patterns> A comma separated list of patterns to ignore files and directories'.PHP_EOL; |
|
1468 | - echo ' <processes> How many files should be fixed simultaneously (default is 1)'.PHP_EOL; |
|
1469 | - echo ' <severity> The minimum severity required to fix an error or warning'.PHP_EOL; |
|
1470 | - echo ' <sniffs> A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL; |
|
1471 | - echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; |
|
1472 | - echo ' <standard> The name or path of the coding standard to use'.PHP_EOL; |
|
1473 | - echo ' <stdinPath> If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; |
|
1474 | - echo ' <suffix> Write modified files to a filename using this suffix'.PHP_EOL; |
|
1475 | - echo ' ("diff" and "patch" are not used in this mode)'.PHP_EOL; |
|
1476 | - echo ' <tabWidth> The number of spaces each tab represents'.PHP_EOL; |
|
1477 | - |
|
1478 | - }//end printPHPCBFUsage() |
|
1479 | - |
|
1480 | - |
|
1481 | - /** |
|
1482 | - * Get a single config value. |
|
1483 | - * |
|
1484 | - * @param string $key The name of the config value. |
|
1485 | - * |
|
1486 | - * @return string|null |
|
1487 | - * @see setConfigData() |
|
1488 | - * @see getAllConfigData() |
|
1489 | - */ |
|
1490 | - public static function getConfigData($key) |
|
1491 | - { |
|
1492 | - $phpCodeSnifferConfig = self::getAllConfigData(); |
|
1493 | - |
|
1494 | - if ($phpCodeSnifferConfig === null) { |
|
1495 | - return null; |
|
1496 | - } |
|
1497 | - |
|
1498 | - if (isset($phpCodeSnifferConfig[$key]) === false) { |
|
1499 | - return null; |
|
1500 | - } |
|
1501 | - |
|
1502 | - return $phpCodeSnifferConfig[$key]; |
|
1503 | - |
|
1504 | - }//end getConfigData() |
|
1505 | - |
|
1506 | - |
|
1507 | - /** |
|
1508 | - * Get the path to an executable utility. |
|
1509 | - * |
|
1510 | - * @param string $name The name of the executable utility. |
|
1511 | - * |
|
1512 | - * @return string|null |
|
1513 | - * @see getConfigData() |
|
1514 | - */ |
|
1515 | - public static function getExecutablePath($name) |
|
1516 | - { |
|
1517 | - $data = self::getConfigData($name.'_path'); |
|
1518 | - if ($data !== null) { |
|
1519 | - return $data; |
|
1520 | - } |
|
1521 | - |
|
1522 | - if ($name === "php") { |
|
1523 | - // For php, we know the executable path. There's no need to look it up. |
|
1524 | - return PHP_BINARY; |
|
1525 | - } |
|
1526 | - |
|
1527 | - if (array_key_exists($name, self::$executablePaths) === true) { |
|
1528 | - return self::$executablePaths[$name]; |
|
1529 | - } |
|
1530 | - |
|
1531 | - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
|
1532 | - $cmd = 'where '.escapeshellarg($name).' 2> nul'; |
|
1533 | - } else { |
|
1534 | - $cmd = 'which '.escapeshellarg($name).' 2> /dev/null'; |
|
1535 | - } |
|
1536 | - |
|
1537 | - $result = exec($cmd, $output, $retVal); |
|
1538 | - if ($retVal !== 0) { |
|
1539 | - $result = null; |
|
1540 | - } |
|
1541 | - |
|
1542 | - self::$executablePaths[$name] = $result; |
|
1543 | - return $result; |
|
1544 | - |
|
1545 | - }//end getExecutablePath() |
|
1546 | - |
|
1547 | - |
|
1548 | - /** |
|
1549 | - * Set a single config value. |
|
1550 | - * |
|
1551 | - * @param string $key The name of the config value. |
|
1552 | - * @param string|null $value The value to set. If null, the config |
|
1553 | - * entry is deleted, reverting it to the |
|
1554 | - * default value. |
|
1555 | - * @param boolean $temp Set this config data temporarily for this |
|
1556 | - * script run. This will not write the config |
|
1557 | - * data to the config file. |
|
1558 | - * |
|
1559 | - * @return bool |
|
1560 | - * @see getConfigData() |
|
1561 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written. |
|
1562 | - */ |
|
1563 | - public static function setConfigData($key, $value, $temp=false) |
|
1564 | - { |
|
1565 | - if (isset(self::$overriddenDefaults['runtime-set']) === true |
|
1566 | - && isset(self::$overriddenDefaults['runtime-set'][$key]) === true |
|
1567 | - ) { |
|
1568 | - return false; |
|
1569 | - } |
|
1570 | - |
|
1571 | - if ($temp === false) { |
|
1572 | - $path = ''; |
|
1573 | - if (is_callable('\Phar::running') === true) { |
|
1574 | - $path = \Phar::running(false); |
|
1575 | - } |
|
1576 | - |
|
1577 | - if ($path !== '') { |
|
1578 | - $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1579 | - } else { |
|
1580 | - $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1581 | - if (is_file($configFile) === false |
|
1582 | - && strpos('@data_dir@', '@data_dir') === false |
|
1583 | - ) { |
|
1584 | - // If data_dir was replaced, this is a PEAR install and we can |
|
1585 | - // use the PEAR data dir to store the conf file. |
|
1586 | - $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; |
|
1587 | - } |
|
1588 | - } |
|
1589 | - |
|
1590 | - if (is_file($configFile) === true |
|
1591 | - && is_writable($configFile) === false |
|
1592 | - ) { |
|
1593 | - $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL; |
|
1594 | - throw new DeepExitException($error, 3); |
|
1595 | - } |
|
1596 | - }//end if |
|
1597 | - |
|
1598 | - $phpCodeSnifferConfig = self::getAllConfigData(); |
|
1599 | - |
|
1600 | - if ($value === null) { |
|
1601 | - if (isset($phpCodeSnifferConfig[$key]) === true) { |
|
1602 | - unset($phpCodeSnifferConfig[$key]); |
|
1603 | - } |
|
1604 | - } else { |
|
1605 | - $phpCodeSnifferConfig[$key] = $value; |
|
1606 | - } |
|
1607 | - |
|
1608 | - if ($temp === false) { |
|
1609 | - $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; |
|
1610 | - $output .= var_export($phpCodeSnifferConfig, true); |
|
1611 | - $output .= "\n?".'>'; |
|
1612 | - |
|
1613 | - if (file_put_contents($configFile, $output) === false) { |
|
1614 | - $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL; |
|
1615 | - throw new DeepExitException($error, 3); |
|
1616 | - } |
|
1617 | - |
|
1618 | - self::$configDataFile = $configFile; |
|
1619 | - } |
|
1620 | - |
|
1621 | - self::$configData = $phpCodeSnifferConfig; |
|
1622 | - |
|
1623 | - // If the installed paths are being set, make sure all known |
|
1624 | - // standards paths are added to the autoloader. |
|
1625 | - if ($key === 'installed_paths') { |
|
1626 | - $installedStandards = Util\Standards::getInstalledStandardDetails(); |
|
1627 | - foreach ($installedStandards as $name => $details) { |
|
1628 | - Autoload::addSearchPath($details['path'], $details['namespace']); |
|
1629 | - } |
|
1630 | - } |
|
1631 | - |
|
1632 | - return true; |
|
1633 | - |
|
1634 | - }//end setConfigData() |
|
1635 | - |
|
1636 | - |
|
1637 | - /** |
|
1638 | - * Get all config data. |
|
1639 | - * |
|
1640 | - * @return array<string, string> |
|
1641 | - * @see getConfigData() |
|
1642 | - */ |
|
1643 | - public static function getAllConfigData() |
|
1644 | - { |
|
1645 | - if (self::$configData !== null) { |
|
1646 | - return self::$configData; |
|
1647 | - } |
|
1648 | - |
|
1649 | - $path = ''; |
|
1650 | - if (is_callable('\Phar::running') === true) { |
|
1651 | - $path = \Phar::running(false); |
|
1652 | - } |
|
1653 | - |
|
1654 | - if ($path !== '') { |
|
1655 | - $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1656 | - } else { |
|
1657 | - $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1658 | - if (is_file($configFile) === false |
|
1659 | - && strpos('@data_dir@', '@data_dir') === false |
|
1660 | - ) { |
|
1661 | - $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; |
|
1662 | - } |
|
1663 | - } |
|
1664 | - |
|
1665 | - if (is_file($configFile) === false) { |
|
1666 | - self::$configData = []; |
|
1667 | - return []; |
|
1668 | - } |
|
1669 | - |
|
1670 | - if (is_readable($configFile) === false) { |
|
1671 | - $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL; |
|
1672 | - throw new DeepExitException($error, 3); |
|
1673 | - } |
|
1674 | - |
|
1675 | - include $configFile; |
|
1676 | - self::$configDataFile = $configFile; |
|
1677 | - self::$configData = $phpCodeSnifferConfig; |
|
1678 | - return self::$configData; |
|
1679 | - |
|
1680 | - }//end getAllConfigData() |
|
1681 | - |
|
1682 | - |
|
1683 | - /** |
|
1684 | - * Prints out the gathered config data. |
|
1685 | - * |
|
1686 | - * @param array $data The config data to print. |
|
1687 | - * |
|
1688 | - * @return void |
|
1689 | - */ |
|
1690 | - public function printConfigData($data) |
|
1691 | - { |
|
1692 | - $max = 0; |
|
1693 | - $keys = array_keys($data); |
|
1694 | - foreach ($keys as $key) { |
|
1695 | - $len = strlen($key); |
|
1696 | - if (strlen($key) > $max) { |
|
1697 | - $max = $len; |
|
1698 | - } |
|
1699 | - } |
|
1700 | - |
|
1701 | - if ($max === 0) { |
|
1702 | - return; |
|
1703 | - } |
|
1704 | - |
|
1705 | - $max += 2; |
|
1706 | - ksort($data); |
|
1707 | - foreach ($data as $name => $value) { |
|
1708 | - echo str_pad($name.': ', $max).$value.PHP_EOL; |
|
1709 | - } |
|
1710 | - |
|
1711 | - }//end printConfigData() |
|
21 | + /** |
|
22 | + * The current version. |
|
23 | + * |
|
24 | + * @var string |
|
25 | + */ |
|
26 | + const VERSION = '3.4.2'; |
|
27 | + |
|
28 | + /** |
|
29 | + * Package stability; either stable, beta or alpha. |
|
30 | + * |
|
31 | + * @var string |
|
32 | + */ |
|
33 | + const STABILITY = 'stable'; |
|
34 | + |
|
35 | + /** |
|
36 | + * An array of settings that PHPCS and PHPCBF accept. |
|
37 | + * |
|
38 | + * This array is not meant to be accessed directly. Instead, use the settings |
|
39 | + * as if they are class member vars so the __get() and __set() magic methods |
|
40 | + * can be used to validate the values. For example, to set the verbosity level to |
|
41 | + * level 2, use $this->verbosity = 2; instead of accessing this property directly. |
|
42 | + * |
|
43 | + * The list of settings are: |
|
44 | + * |
|
45 | + * string[] files The files and directories to check. |
|
46 | + * string[] standards The standards being used for checking. |
|
47 | + * int verbosity How verbose the output should be. |
|
48 | + * 0: no unnecessary output |
|
49 | + * 1: basic output for files being checked |
|
50 | + * 2: ruleset and file parsing output |
|
51 | + * 3: sniff execution output |
|
52 | + * bool interactive Enable interactive checking mode. |
|
53 | + * bool parallel Check files in parallel. |
|
54 | + * bool cache Enable the use of the file cache. |
|
55 | + * bool cacheFile A file where the cache data should be written |
|
56 | + * bool colors Display colours in output. |
|
57 | + * bool explain Explain the coding standards. |
|
58 | + * bool local Process local files in directories only (no recursion). |
|
59 | + * bool showSources Show sniff source codes in report output. |
|
60 | + * bool showProgress Show basic progress information while running. |
|
61 | + * bool quiet Quiet mode; disables progress and verbose output. |
|
62 | + * bool annotations Process phpcs: annotations. |
|
63 | + * int tabWidth How many spaces each tab is worth. |
|
64 | + * string encoding The encoding of the files being checked. |
|
65 | + * string[] sniffs The sniffs that should be used for checking. |
|
66 | + * If empty, all sniffs in the supplied standards will be used. |
|
67 | + * string[] exclude The sniffs that should be excluded from checking. |
|
68 | + * If empty, all sniffs in the supplied standards will be used. |
|
69 | + * string[] ignored Regular expressions used to ignore files and folders during checking. |
|
70 | + * string reportFile A file where the report output should be written. |
|
71 | + * string generator The documentation generator to use. |
|
72 | + * string filter The filter to use for the run. |
|
73 | + * string[] bootstrap One of more files to include before the run begins. |
|
74 | + * int reportWidth The maximum number of columns that reports should use for output. |
|
75 | + * Set to "auto" for have this value changed to the width of the terminal. |
|
76 | + * int errorSeverity The minimum severity an error must have to be displayed. |
|
77 | + * int warningSeverity The minimum severity a warning must have to be displayed. |
|
78 | + * bool recordErrors Record the content of error messages as well as error counts. |
|
79 | + * string suffix A suffix to add to fixed files. |
|
80 | + * string basepath A file system location to strip from the paths of files shown in reports. |
|
81 | + * bool stdin Read content from STDIN instead of supplied files. |
|
82 | + * string stdinContent Content passed directly to PHPCS on STDIN. |
|
83 | + * string stdinPath The path to use for content passed on STDIN. |
|
84 | + * |
|
85 | + * array<string, string> extensions File extensions that should be checked, and what tokenizer to use. |
|
86 | + * E.g., array('inc' => 'PHP'); |
|
87 | + * array<string, string|null> reports The reports to use for printing output after the run. |
|
88 | + * The format of the array is: |
|
89 | + * array( |
|
90 | + * 'reportName1' => 'outputFile', |
|
91 | + * 'reportName2' => null, |
|
92 | + * ); |
|
93 | + * If the array value is NULL, the report will be written to the screen. |
|
94 | + * |
|
95 | + * string[] unknown Any arguments gathered on the command line that are unknown to us. |
|
96 | + * E.g., using `phpcs -c` will give array('c'); |
|
97 | + * |
|
98 | + * @var array<string, mixed> |
|
99 | + */ |
|
100 | + private $settings = [ |
|
101 | + 'files' => null, |
|
102 | + 'standards' => null, |
|
103 | + 'verbosity' => null, |
|
104 | + 'interactive' => null, |
|
105 | + 'parallel' => null, |
|
106 | + 'cache' => null, |
|
107 | + 'cacheFile' => null, |
|
108 | + 'colors' => null, |
|
109 | + 'explain' => null, |
|
110 | + 'local' => null, |
|
111 | + 'showSources' => null, |
|
112 | + 'showProgress' => null, |
|
113 | + 'quiet' => null, |
|
114 | + 'annotations' => null, |
|
115 | + 'tabWidth' => null, |
|
116 | + 'encoding' => null, |
|
117 | + 'extensions' => null, |
|
118 | + 'sniffs' => null, |
|
119 | + 'exclude' => null, |
|
120 | + 'ignored' => null, |
|
121 | + 'reportFile' => null, |
|
122 | + 'generator' => null, |
|
123 | + 'filter' => null, |
|
124 | + 'bootstrap' => null, |
|
125 | + 'reports' => null, |
|
126 | + 'basepath' => null, |
|
127 | + 'reportWidth' => null, |
|
128 | + 'errorSeverity' => null, |
|
129 | + 'warningSeverity' => null, |
|
130 | + 'recordErrors' => null, |
|
131 | + 'suffix' => null, |
|
132 | + 'stdin' => null, |
|
133 | + 'stdinContent' => null, |
|
134 | + 'stdinPath' => null, |
|
135 | + 'unknown' => null, |
|
136 | + ]; |
|
137 | + |
|
138 | + /** |
|
139 | + * Whether or not to kill the process when an unknown command line arg is found. |
|
140 | + * |
|
141 | + * If FALSE, arguments that are not command line options or file/directory paths |
|
142 | + * will be ignored and execution will continue. These values will be stored in |
|
143 | + * $this->unknown. |
|
144 | + * |
|
145 | + * @var boolean |
|
146 | + */ |
|
147 | + public $dieOnUnknownArg; |
|
148 | + |
|
149 | + /** |
|
150 | + * The current command line arguments we are processing. |
|
151 | + * |
|
152 | + * @var string[] |
|
153 | + */ |
|
154 | + private $cliArgs = []; |
|
155 | + |
|
156 | + /** |
|
157 | + * Command line values that the user has supplied directly. |
|
158 | + * |
|
159 | + * @var array<string, TRUE> |
|
160 | + */ |
|
161 | + private static $overriddenDefaults = []; |
|
162 | + |
|
163 | + /** |
|
164 | + * Config file data that has been loaded for the run. |
|
165 | + * |
|
166 | + * @var array<string, string> |
|
167 | + */ |
|
168 | + private static $configData = null; |
|
169 | + |
|
170 | + /** |
|
171 | + * The full path to the config data file that has been loaded. |
|
172 | + * |
|
173 | + * @var string |
|
174 | + */ |
|
175 | + private static $configDataFile = null; |
|
176 | + |
|
177 | + /** |
|
178 | + * Automatically discovered executable utility paths. |
|
179 | + * |
|
180 | + * @var array<string, string> |
|
181 | + */ |
|
182 | + private static $executablePaths = []; |
|
183 | + |
|
184 | + |
|
185 | + /** |
|
186 | + * Get the value of an inaccessible property. |
|
187 | + * |
|
188 | + * @param string $name The name of the property. |
|
189 | + * |
|
190 | + * @return mixed |
|
191 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. |
|
192 | + */ |
|
193 | + public function __get($name) |
|
194 | + { |
|
195 | + if (array_key_exists($name, $this->settings) === false) { |
|
196 | + throw new RuntimeException("ERROR: unable to get value of property \"$name\""); |
|
197 | + } |
|
198 | + |
|
199 | + return $this->settings[$name]; |
|
200 | + |
|
201 | + }//end __get() |
|
202 | + |
|
203 | + |
|
204 | + /** |
|
205 | + * Set the value of an inaccessible property. |
|
206 | + * |
|
207 | + * @param string $name The name of the property. |
|
208 | + * @param mixed $value The value of the property. |
|
209 | + * |
|
210 | + * @return void |
|
211 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the setting name is invalid. |
|
212 | + */ |
|
213 | + public function __set($name, $value) |
|
214 | + { |
|
215 | + if (array_key_exists($name, $this->settings) === false) { |
|
216 | + throw new RuntimeException("Can't __set() $name; setting doesn't exist"); |
|
217 | + } |
|
218 | + |
|
219 | + switch ($name) { |
|
220 | + case 'reportWidth' : |
|
221 | + // Support auto terminal width. |
|
222 | + if ($value === 'auto' && preg_match('|\d+ (\d+)|', shell_exec('stty size 2>&1'), $matches) === 1) { |
|
223 | + $value = (int) $matches[1]; |
|
224 | + } else { |
|
225 | + $value = (int) $value; |
|
226 | + } |
|
227 | + break; |
|
228 | + case 'standards' : |
|
229 | + $cleaned = []; |
|
230 | + |
|
231 | + // Check if the standard name is valid, or if the case is invalid. |
|
232 | + $installedStandards = Util\Standards::getInstalledStandards(); |
|
233 | + foreach ($value as $standard) { |
|
234 | + foreach ($installedStandards as $validStandard) { |
|
235 | + if (strtolower($standard) === strtolower($validStandard)) { |
|
236 | + $standard = $validStandard; |
|
237 | + break; |
|
238 | + } |
|
239 | + } |
|
240 | + |
|
241 | + $cleaned[] = $standard; |
|
242 | + } |
|
243 | + |
|
244 | + $value = $cleaned; |
|
245 | + break; |
|
246 | + default : |
|
247 | + // No validation required. |
|
248 | + break; |
|
249 | + }//end switch |
|
250 | + |
|
251 | + $this->settings[$name] = $value; |
|
252 | + |
|
253 | + }//end __set() |
|
254 | + |
|
255 | + |
|
256 | + /** |
|
257 | + * Check if the value of an inaccessible property is set. |
|
258 | + * |
|
259 | + * @param string $name The name of the property. |
|
260 | + * |
|
261 | + * @return bool |
|
262 | + */ |
|
263 | + public function __isset($name) |
|
264 | + { |
|
265 | + return isset($this->settings[$name]); |
|
266 | + |
|
267 | + }//end __isset() |
|
268 | + |
|
269 | + |
|
270 | + /** |
|
271 | + * Unset the value of an inaccessible property. |
|
272 | + * |
|
273 | + * @param string $name The name of the property. |
|
274 | + * |
|
275 | + * @return void |
|
276 | + */ |
|
277 | + public function __unset($name) |
|
278 | + { |
|
279 | + $this->settings[$name] = null; |
|
280 | + |
|
281 | + }//end __unset() |
|
282 | + |
|
283 | + |
|
284 | + /** |
|
285 | + * Get the array of all config settings. |
|
286 | + * |
|
287 | + * @return array<string, mixed> |
|
288 | + */ |
|
289 | + public function getSettings() |
|
290 | + { |
|
291 | + return $this->settings; |
|
292 | + |
|
293 | + }//end getSettings() |
|
294 | + |
|
295 | + |
|
296 | + /** |
|
297 | + * Set the array of all config settings. |
|
298 | + * |
|
299 | + * @param array<string, mixed> $settings The array of config settings. |
|
300 | + * |
|
301 | + * @return void |
|
302 | + */ |
|
303 | + public function setSettings($settings) |
|
304 | + { |
|
305 | + return $this->settings = $settings; |
|
306 | + |
|
307 | + }//end setSettings() |
|
308 | + |
|
309 | + |
|
310 | + /** |
|
311 | + * Creates a Config object and populates it with command line values. |
|
312 | + * |
|
313 | + * @param array $cliArgs An array of values gathered from CLI args. |
|
314 | + * @param bool $dieOnUnknownArg Whether or not to kill the process when an |
|
315 | + * unknown command line arg is found. |
|
316 | + * |
|
317 | + * @return void |
|
318 | + */ |
|
319 | + public function __construct(array $cliArgs=[], $dieOnUnknownArg=true) |
|
320 | + { |
|
321 | + if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { |
|
322 | + // Let everything through during testing so that we can |
|
323 | + // make use of PHPUnit command line arguments as well. |
|
324 | + $this->dieOnUnknownArg = false; |
|
325 | + } else { |
|
326 | + $this->dieOnUnknownArg = $dieOnUnknownArg; |
|
327 | + } |
|
328 | + |
|
329 | + if (empty($cliArgs) === true) { |
|
330 | + $cliArgs = $_SERVER['argv']; |
|
331 | + array_shift($cliArgs); |
|
332 | + } |
|
333 | + |
|
334 | + $this->restoreDefaults(); |
|
335 | + $this->setCommandLineValues($cliArgs); |
|
336 | + |
|
337 | + if (isset(self::$overriddenDefaults['standards']) === false) { |
|
338 | + // They did not supply a standard to use. |
|
339 | + // Look for a default ruleset in the current directory or higher. |
|
340 | + $currentDir = getcwd(); |
|
341 | + |
|
342 | + $defaultFiles = [ |
|
343 | + '.phpcs.xml', |
|
344 | + 'phpcs.xml', |
|
345 | + '.phpcs.xml.dist', |
|
346 | + 'phpcs.xml.dist', |
|
347 | + ]; |
|
348 | + |
|
349 | + do { |
|
350 | + foreach ($defaultFiles as $defaultFilename) { |
|
351 | + $default = $currentDir.DIRECTORY_SEPARATOR.$defaultFilename; |
|
352 | + if (is_file($default) === true) { |
|
353 | + $this->standards = [$default]; |
|
354 | + break(2); |
|
355 | + } |
|
356 | + } |
|
357 | + |
|
358 | + $lastDir = $currentDir; |
|
359 | + $currentDir = dirname($currentDir); |
|
360 | + } while ($currentDir !== '.' && $currentDir !== $lastDir); |
|
361 | + }//end if |
|
362 | + |
|
363 | + if (defined('STDIN') === false |
|
364 | + || strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' |
|
365 | + ) { |
|
366 | + return; |
|
367 | + } |
|
368 | + |
|
369 | + $handle = fopen('php://stdin', 'r'); |
|
370 | + |
|
371 | + // Check for content on STDIN. |
|
372 | + if ($this->stdin === true |
|
373 | + || (Util\Common::isStdinATTY() === false |
|
374 | + && feof($handle) === false) |
|
375 | + ) { |
|
376 | + $readStreams = [$handle]; |
|
377 | + $writeSteams = null; |
|
378 | + |
|
379 | + $fileContents = ''; |
|
380 | + while (is_resource($handle) === true && feof($handle) === false) { |
|
381 | + // Set a timeout of 200ms. |
|
382 | + if (stream_select($readStreams, $writeSteams, $writeSteams, 0, 200000) === 0) { |
|
383 | + break; |
|
384 | + } |
|
385 | + |
|
386 | + $fileContents .= fgets($handle); |
|
387 | + } |
|
388 | + |
|
389 | + if (trim($fileContents) !== '') { |
|
390 | + $this->stdin = true; |
|
391 | + $this->stdinContent = $fileContents; |
|
392 | + self::$overriddenDefaults['stdin'] = true; |
|
393 | + self::$overriddenDefaults['stdinContent'] = true; |
|
394 | + } |
|
395 | + }//end if |
|
396 | + |
|
397 | + fclose($handle); |
|
398 | + |
|
399 | + }//end __construct() |
|
400 | + |
|
401 | + |
|
402 | + /** |
|
403 | + * Set the command line values. |
|
404 | + * |
|
405 | + * @param array $args An array of command line arguments to set. |
|
406 | + * |
|
407 | + * @return void |
|
408 | + */ |
|
409 | + public function setCommandLineValues($args) |
|
410 | + { |
|
411 | + $this->cliArgs = $args; |
|
412 | + $numArgs = count($args); |
|
413 | + |
|
414 | + for ($i = 0; $i < $numArgs; $i++) { |
|
415 | + $arg = $this->cliArgs[$i]; |
|
416 | + if ($arg === '') { |
|
417 | + continue; |
|
418 | + } |
|
419 | + |
|
420 | + if ($arg{0} === '-') { |
|
421 | + if ($arg === '-') { |
|
422 | + // Asking to read from STDIN. |
|
423 | + $this->stdin = true; |
|
424 | + self::$overriddenDefaults['stdin'] = true; |
|
425 | + continue; |
|
426 | + } |
|
427 | + |
|
428 | + if ($arg === '--') { |
|
429 | + // Empty argument, ignore it. |
|
430 | + continue; |
|
431 | + } |
|
432 | + |
|
433 | + if ($arg{1} === '-') { |
|
434 | + $this->processLongArgument(substr($arg, 2), $i); |
|
435 | + } else { |
|
436 | + $switches = str_split($arg); |
|
437 | + foreach ($switches as $switch) { |
|
438 | + if ($switch === '-') { |
|
439 | + continue; |
|
440 | + } |
|
441 | + |
|
442 | + $this->processShortArgument($switch, $i); |
|
443 | + } |
|
444 | + } |
|
445 | + } else { |
|
446 | + $this->processUnknownArgument($arg, $i); |
|
447 | + }//end if |
|
448 | + }//end for |
|
449 | + |
|
450 | + }//end setCommandLineValues() |
|
451 | + |
|
452 | + |
|
453 | + /** |
|
454 | + * Restore default values for all possible command line arguments. |
|
455 | + * |
|
456 | + * @return array |
|
457 | + */ |
|
458 | + public function restoreDefaults() |
|
459 | + { |
|
460 | + $this->files = []; |
|
461 | + $this->standards = ['PEAR']; |
|
462 | + $this->verbosity = 0; |
|
463 | + $this->interactive = false; |
|
464 | + $this->cache = false; |
|
465 | + $this->cacheFile = null; |
|
466 | + $this->colors = false; |
|
467 | + $this->explain = false; |
|
468 | + $this->local = false; |
|
469 | + $this->showSources = false; |
|
470 | + $this->showProgress = false; |
|
471 | + $this->quiet = false; |
|
472 | + $this->annotations = true; |
|
473 | + $this->parallel = 1; |
|
474 | + $this->tabWidth = 0; |
|
475 | + $this->encoding = 'utf-8'; |
|
476 | + $this->extensions = [ |
|
477 | + 'php' => 'PHP', |
|
478 | + 'inc' => 'PHP', |
|
479 | + 'js' => 'JS', |
|
480 | + 'css' => 'CSS', |
|
481 | + ]; |
|
482 | + $this->sniffs = []; |
|
483 | + $this->exclude = []; |
|
484 | + $this->ignored = []; |
|
485 | + $this->reportFile = null; |
|
486 | + $this->generator = null; |
|
487 | + $this->filter = null; |
|
488 | + $this->bootstrap = []; |
|
489 | + $this->basepath = null; |
|
490 | + $this->reports = ['full' => null]; |
|
491 | + $this->reportWidth = 'auto'; |
|
492 | + $this->errorSeverity = 5; |
|
493 | + $this->warningSeverity = 5; |
|
494 | + $this->recordErrors = true; |
|
495 | + $this->suffix = ''; |
|
496 | + $this->stdin = false; |
|
497 | + $this->stdinContent = null; |
|
498 | + $this->stdinPath = null; |
|
499 | + $this->unknown = []; |
|
500 | + |
|
501 | + $standard = self::getConfigData('default_standard'); |
|
502 | + if ($standard !== null) { |
|
503 | + $this->standards = explode(',', $standard); |
|
504 | + } |
|
505 | + |
|
506 | + $reportFormat = self::getConfigData('report_format'); |
|
507 | + if ($reportFormat !== null) { |
|
508 | + $this->reports = [$reportFormat => null]; |
|
509 | + } |
|
510 | + |
|
511 | + $tabWidth = self::getConfigData('tab_width'); |
|
512 | + if ($tabWidth !== null) { |
|
513 | + $this->tabWidth = (int) $tabWidth; |
|
514 | + } |
|
515 | + |
|
516 | + $encoding = self::getConfigData('encoding'); |
|
517 | + if ($encoding !== null) { |
|
518 | + $this->encoding = strtolower($encoding); |
|
519 | + } |
|
520 | + |
|
521 | + $severity = self::getConfigData('severity'); |
|
522 | + if ($severity !== null) { |
|
523 | + $this->errorSeverity = (int) $severity; |
|
524 | + $this->warningSeverity = (int) $severity; |
|
525 | + } |
|
526 | + |
|
527 | + $severity = self::getConfigData('error_severity'); |
|
528 | + if ($severity !== null) { |
|
529 | + $this->errorSeverity = (int) $severity; |
|
530 | + } |
|
531 | + |
|
532 | + $severity = self::getConfigData('warning_severity'); |
|
533 | + if ($severity !== null) { |
|
534 | + $this->warningSeverity = (int) $severity; |
|
535 | + } |
|
536 | + |
|
537 | + $showWarnings = self::getConfigData('show_warnings'); |
|
538 | + if ($showWarnings !== null) { |
|
539 | + $showWarnings = (bool) $showWarnings; |
|
540 | + if ($showWarnings === false) { |
|
541 | + $this->warningSeverity = 0; |
|
542 | + } |
|
543 | + } |
|
544 | + |
|
545 | + $reportWidth = self::getConfigData('report_width'); |
|
546 | + if ($reportWidth !== null) { |
|
547 | + $this->reportWidth = $reportWidth; |
|
548 | + } |
|
549 | + |
|
550 | + $showProgress = self::getConfigData('show_progress'); |
|
551 | + if ($showProgress !== null) { |
|
552 | + $this->showProgress = (bool) $showProgress; |
|
553 | + } |
|
554 | + |
|
555 | + $quiet = self::getConfigData('quiet'); |
|
556 | + if ($quiet !== null) { |
|
557 | + $this->quiet = (bool) $quiet; |
|
558 | + } |
|
559 | + |
|
560 | + $colors = self::getConfigData('colors'); |
|
561 | + if ($colors !== null) { |
|
562 | + $this->colors = (bool) $colors; |
|
563 | + } |
|
564 | + |
|
565 | + if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { |
|
566 | + $cache = self::getConfigData('cache'); |
|
567 | + if ($cache !== null) { |
|
568 | + $this->cache = (bool) $cache; |
|
569 | + } |
|
570 | + |
|
571 | + $parallel = self::getConfigData('parallel'); |
|
572 | + if ($parallel !== null) { |
|
573 | + $this->parallel = max((int) $parallel, 1); |
|
574 | + } |
|
575 | + } |
|
576 | + |
|
577 | + }//end restoreDefaults() |
|
578 | + |
|
579 | + |
|
580 | + /** |
|
581 | + * Processes a short (-e) command line argument. |
|
582 | + * |
|
583 | + * @param string $arg The command line argument. |
|
584 | + * @param int $pos The position of the argument on the command line. |
|
585 | + * |
|
586 | + * @return void |
|
587 | + */ |
|
588 | + public function processShortArgument($arg, $pos) |
|
589 | + { |
|
590 | + switch ($arg) { |
|
591 | + case 'h': |
|
592 | + case '?': |
|
593 | + ob_start(); |
|
594 | + $this->printUsage(); |
|
595 | + $output = ob_get_contents(); |
|
596 | + ob_end_clean(); |
|
597 | + throw new DeepExitException($output, 0); |
|
598 | + case 'i' : |
|
599 | + ob_start(); |
|
600 | + Util\Standards::printInstalledStandards(); |
|
601 | + $output = ob_get_contents(); |
|
602 | + ob_end_clean(); |
|
603 | + throw new DeepExitException($output, 0); |
|
604 | + case 'v' : |
|
605 | + if ($this->quiet === true) { |
|
606 | + // Ignore when quiet mode is enabled. |
|
607 | + break; |
|
608 | + } |
|
609 | + |
|
610 | + $this->verbosity++; |
|
611 | + self::$overriddenDefaults['verbosity'] = true; |
|
612 | + break; |
|
613 | + case 'l' : |
|
614 | + $this->local = true; |
|
615 | + self::$overriddenDefaults['local'] = true; |
|
616 | + break; |
|
617 | + case 's' : |
|
618 | + $this->showSources = true; |
|
619 | + self::$overriddenDefaults['showSources'] = true; |
|
620 | + break; |
|
621 | + case 'a' : |
|
622 | + $this->interactive = true; |
|
623 | + self::$overriddenDefaults['interactive'] = true; |
|
624 | + break; |
|
625 | + case 'e': |
|
626 | + $this->explain = true; |
|
627 | + self::$overriddenDefaults['explain'] = true; |
|
628 | + break; |
|
629 | + case 'p' : |
|
630 | + if ($this->quiet === true) { |
|
631 | + // Ignore when quiet mode is enabled. |
|
632 | + break; |
|
633 | + } |
|
634 | + |
|
635 | + $this->showProgress = true; |
|
636 | + self::$overriddenDefaults['showProgress'] = true; |
|
637 | + break; |
|
638 | + case 'q' : |
|
639 | + // Quiet mode disables a few other settings as well. |
|
640 | + $this->quiet = true; |
|
641 | + $this->showProgress = false; |
|
642 | + $this->verbosity = 0; |
|
643 | + |
|
644 | + self::$overriddenDefaults['quiet'] = true; |
|
645 | + break; |
|
646 | + case 'm' : |
|
647 | + $this->recordErrors = false; |
|
648 | + self::$overriddenDefaults['recordErrors'] = true; |
|
649 | + break; |
|
650 | + case 'd' : |
|
651 | + $ini = explode('=', $this->cliArgs[($pos + 1)]); |
|
652 | + $this->cliArgs[($pos + 1)] = ''; |
|
653 | + if (isset($ini[1]) === true) { |
|
654 | + ini_set($ini[0], $ini[1]); |
|
655 | + } else { |
|
656 | + ini_set($ini[0], true); |
|
657 | + } |
|
658 | + break; |
|
659 | + case 'n' : |
|
660 | + if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
661 | + $this->warningSeverity = 0; |
|
662 | + self::$overriddenDefaults['warningSeverity'] = true; |
|
663 | + } |
|
664 | + break; |
|
665 | + case 'w' : |
|
666 | + if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
667 | + $this->warningSeverity = $this->errorSeverity; |
|
668 | + self::$overriddenDefaults['warningSeverity'] = true; |
|
669 | + } |
|
670 | + break; |
|
671 | + default: |
|
672 | + if ($this->dieOnUnknownArg === false) { |
|
673 | + $unknown = $this->unknown; |
|
674 | + $unknown[] = $arg; |
|
675 | + $this->unknown = $unknown; |
|
676 | + } else { |
|
677 | + $this->processUnknownArgument('-'.$arg, $pos); |
|
678 | + } |
|
679 | + }//end switch |
|
680 | + |
|
681 | + }//end processShortArgument() |
|
682 | + |
|
683 | + |
|
684 | + /** |
|
685 | + * Processes a long (--example) command line argument. |
|
686 | + * |
|
687 | + * @param string $arg The command line argument. |
|
688 | + * @param int $pos The position of the argument on the command line. |
|
689 | + * |
|
690 | + * @return void |
|
691 | + */ |
|
692 | + public function processLongArgument($arg, $pos) |
|
693 | + { |
|
694 | + switch ($arg) { |
|
695 | + case 'help': |
|
696 | + ob_start(); |
|
697 | + $this->printUsage(); |
|
698 | + $output = ob_get_contents(); |
|
699 | + ob_end_clean(); |
|
700 | + throw new DeepExitException($output, 0); |
|
701 | + case 'version': |
|
702 | + $output = 'PHP_CodeSniffer version '.self::VERSION.' ('.self::STABILITY.') '; |
|
703 | + $output .= 'by Squiz (http://www.squiz.net)'.PHP_EOL; |
|
704 | + throw new DeepExitException($output, 0); |
|
705 | + case 'colors': |
|
706 | + if (isset(self::$overriddenDefaults['colors']) === true) { |
|
707 | + break; |
|
708 | + } |
|
709 | + |
|
710 | + $this->colors = true; |
|
711 | + self::$overriddenDefaults['colors'] = true; |
|
712 | + break; |
|
713 | + case 'no-colors': |
|
714 | + if (isset(self::$overriddenDefaults['colors']) === true) { |
|
715 | + break; |
|
716 | + } |
|
717 | + |
|
718 | + $this->colors = false; |
|
719 | + self::$overriddenDefaults['colors'] = true; |
|
720 | + break; |
|
721 | + case 'cache': |
|
722 | + if (isset(self::$overriddenDefaults['cache']) === true) { |
|
723 | + break; |
|
724 | + } |
|
725 | + |
|
726 | + if (defined('PHP_CODESNIFFER_IN_TESTS') === false) { |
|
727 | + $this->cache = true; |
|
728 | + self::$overriddenDefaults['cache'] = true; |
|
729 | + } |
|
730 | + break; |
|
731 | + case 'no-cache': |
|
732 | + if (isset(self::$overriddenDefaults['cache']) === true) { |
|
733 | + break; |
|
734 | + } |
|
735 | + |
|
736 | + $this->cache = false; |
|
737 | + self::$overriddenDefaults['cache'] = true; |
|
738 | + break; |
|
739 | + case 'ignore-annotations': |
|
740 | + if (isset(self::$overriddenDefaults['annotations']) === true) { |
|
741 | + break; |
|
742 | + } |
|
743 | + |
|
744 | + $this->annotations = false; |
|
745 | + self::$overriddenDefaults['annotations'] = true; |
|
746 | + break; |
|
747 | + case 'config-set': |
|
748 | + if (isset($this->cliArgs[($pos + 1)]) === false |
|
749 | + || isset($this->cliArgs[($pos + 2)]) === false |
|
750 | + ) { |
|
751 | + $error = 'ERROR: Setting a config option requires a name and value'.PHP_EOL.PHP_EOL; |
|
752 | + $error .= $this->printShortUsage(true); |
|
753 | + throw new DeepExitException($error, 3); |
|
754 | + } |
|
755 | + |
|
756 | + $key = $this->cliArgs[($pos + 1)]; |
|
757 | + $value = $this->cliArgs[($pos + 2)]; |
|
758 | + $current = self::getConfigData($key); |
|
759 | + |
|
760 | + try { |
|
761 | + $this->setConfigData($key, $value); |
|
762 | + } catch (\Exception $e) { |
|
763 | + throw new DeepExitException($e->getMessage().PHP_EOL, 3); |
|
764 | + } |
|
765 | + |
|
766 | + $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
767 | + |
|
768 | + if ($current === null) { |
|
769 | + $output .= "Config value \"$key\" added successfully".PHP_EOL; |
|
770 | + } else { |
|
771 | + $output .= "Config value \"$key\" updated successfully; old value was \"$current\"".PHP_EOL; |
|
772 | + } |
|
773 | + throw new DeepExitException($output, 0); |
|
774 | + case 'config-delete': |
|
775 | + if (isset($this->cliArgs[($pos + 1)]) === false) { |
|
776 | + $error = 'ERROR: Deleting a config option requires the name of the option'.PHP_EOL.PHP_EOL; |
|
777 | + $error .= $this->printShortUsage(true); |
|
778 | + throw new DeepExitException($error, 3); |
|
779 | + } |
|
780 | + |
|
781 | + $output = 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
782 | + |
|
783 | + $key = $this->cliArgs[($pos + 1)]; |
|
784 | + $current = self::getConfigData($key); |
|
785 | + if ($current === null) { |
|
786 | + $output .= "Config value \"$key\" has not been set".PHP_EOL; |
|
787 | + } else { |
|
788 | + try { |
|
789 | + $this->setConfigData($key, null); |
|
790 | + } catch (\Exception $e) { |
|
791 | + throw new DeepExitException($e->getMessage().PHP_EOL, 3); |
|
792 | + } |
|
793 | + |
|
794 | + $output .= "Config value \"$key\" removed successfully; old value was \"$current\"".PHP_EOL; |
|
795 | + } |
|
796 | + throw new DeepExitException($output, 0); |
|
797 | + case 'config-show': |
|
798 | + ob_start(); |
|
799 | + $data = self::getAllConfigData(); |
|
800 | + echo 'Using config file: '.self::$configDataFile.PHP_EOL.PHP_EOL; |
|
801 | + $this->printConfigData($data); |
|
802 | + $output = ob_get_contents(); |
|
803 | + ob_end_clean(); |
|
804 | + throw new DeepExitException($output, 0); |
|
805 | + case 'runtime-set': |
|
806 | + if (isset($this->cliArgs[($pos + 1)]) === false |
|
807 | + || isset($this->cliArgs[($pos + 2)]) === false |
|
808 | + ) { |
|
809 | + $error = 'ERROR: Setting a runtime config option requires a name and value'.PHP_EOL.PHP_EOL; |
|
810 | + $error .= $this->printShortUsage(true); |
|
811 | + throw new DeepExitException($error, 3); |
|
812 | + } |
|
813 | + |
|
814 | + $key = $this->cliArgs[($pos + 1)]; |
|
815 | + $value = $this->cliArgs[($pos + 2)]; |
|
816 | + $this->cliArgs[($pos + 1)] = ''; |
|
817 | + $this->cliArgs[($pos + 2)] = ''; |
|
818 | + self::setConfigData($key, $value, true); |
|
819 | + if (isset(self::$overriddenDefaults['runtime-set']) === false) { |
|
820 | + self::$overriddenDefaults['runtime-set'] = []; |
|
821 | + } |
|
822 | + |
|
823 | + self::$overriddenDefaults['runtime-set'][$key] = true; |
|
824 | + break; |
|
825 | + default: |
|
826 | + if (substr($arg, 0, 7) === 'sniffs=') { |
|
827 | + if (isset(self::$overriddenDefaults['sniffs']) === true) { |
|
828 | + break; |
|
829 | + } |
|
830 | + |
|
831 | + $sniffs = explode(',', substr($arg, 7)); |
|
832 | + foreach ($sniffs as $sniff) { |
|
833 | + if (substr_count($sniff, '.') !== 2) { |
|
834 | + $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; |
|
835 | + $error .= $this->printShortUsage(true); |
|
836 | + throw new DeepExitException($error, 3); |
|
837 | + } |
|
838 | + } |
|
839 | + |
|
840 | + $this->sniffs = $sniffs; |
|
841 | + self::$overriddenDefaults['sniffs'] = true; |
|
842 | + } else if (substr($arg, 0, 8) === 'exclude=') { |
|
843 | + if (isset(self::$overriddenDefaults['exclude']) === true) { |
|
844 | + break; |
|
845 | + } |
|
846 | + |
|
847 | + $sniffs = explode(',', substr($arg, 8)); |
|
848 | + foreach ($sniffs as $sniff) { |
|
849 | + if (substr_count($sniff, '.') !== 2) { |
|
850 | + $error = 'ERROR: The specified sniff code "'.$sniff.'" is invalid'.PHP_EOL.PHP_EOL; |
|
851 | + $error .= $this->printShortUsage(true); |
|
852 | + throw new DeepExitException($error, 3); |
|
853 | + } |
|
854 | + } |
|
855 | + |
|
856 | + $this->exclude = $sniffs; |
|
857 | + self::$overriddenDefaults['exclude'] = true; |
|
858 | + } else if (defined('PHP_CODESNIFFER_IN_TESTS') === false |
|
859 | + && substr($arg, 0, 6) === 'cache=' |
|
860 | + ) { |
|
861 | + if ((isset(self::$overriddenDefaults['cache']) === true |
|
862 | + && $this->cache === false) |
|
863 | + || isset(self::$overriddenDefaults['cacheFile']) === true |
|
864 | + ) { |
|
865 | + break; |
|
866 | + } |
|
867 | + |
|
868 | + // Turn caching on. |
|
869 | + $this->cache = true; |
|
870 | + self::$overriddenDefaults['cache'] = true; |
|
871 | + |
|
872 | + $this->cacheFile = Util\Common::realpath(substr($arg, 6)); |
|
873 | + |
|
874 | + // It may not exist and return false instead. |
|
875 | + if ($this->cacheFile === false) { |
|
876 | + $this->cacheFile = substr($arg, 6); |
|
877 | + |
|
878 | + $dir = dirname($this->cacheFile); |
|
879 | + if (is_dir($dir) === false) { |
|
880 | + $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
881 | + $error .= $this->printShortUsage(true); |
|
882 | + throw new DeepExitException($error, 3); |
|
883 | + } |
|
884 | + |
|
885 | + if ($dir === '.') { |
|
886 | + // Passed cache file is a file in the current directory. |
|
887 | + $this->cacheFile = getcwd().'/'.basename($this->cacheFile); |
|
888 | + } else { |
|
889 | + if ($dir{0} === '/') { |
|
890 | + // An absolute path. |
|
891 | + $dir = Util\Common::realpath($dir); |
|
892 | + } else { |
|
893 | + $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
894 | + } |
|
895 | + |
|
896 | + if ($dir !== false) { |
|
897 | + // Cache file path is relative. |
|
898 | + $this->cacheFile = $dir.'/'.basename($this->cacheFile); |
|
899 | + } |
|
900 | + } |
|
901 | + }//end if |
|
902 | + |
|
903 | + self::$overriddenDefaults['cacheFile'] = true; |
|
904 | + |
|
905 | + if (is_dir($this->cacheFile) === true) { |
|
906 | + $error = 'ERROR: The specified cache file path "'.$this->cacheFile.'" is a directory'.PHP_EOL.PHP_EOL; |
|
907 | + $error .= $this->printShortUsage(true); |
|
908 | + throw new DeepExitException($error, 3); |
|
909 | + } |
|
910 | + } else if (substr($arg, 0, 10) === 'bootstrap=') { |
|
911 | + $files = explode(',', substr($arg, 10)); |
|
912 | + $bootstrap = []; |
|
913 | + foreach ($files as $file) { |
|
914 | + $path = Util\Common::realpath($file); |
|
915 | + if ($path === false) { |
|
916 | + $error = 'ERROR: The specified bootstrap file "'.$file.'" does not exist'.PHP_EOL.PHP_EOL; |
|
917 | + $error .= $this->printShortUsage(true); |
|
918 | + throw new DeepExitException($error, 3); |
|
919 | + } |
|
920 | + |
|
921 | + $bootstrap[] = $path; |
|
922 | + } |
|
923 | + |
|
924 | + $this->bootstrap = array_merge($this->bootstrap, $bootstrap); |
|
925 | + self::$overriddenDefaults['bootstrap'] = true; |
|
926 | + } else if (substr($arg, 0, 10) === 'file-list=') { |
|
927 | + $fileList = substr($arg, 10); |
|
928 | + $path = Util\Common::realpath($fileList); |
|
929 | + if ($path === false) { |
|
930 | + $error = 'ERROR: The specified file list "'.$fileList.'" does not exist'.PHP_EOL.PHP_EOL; |
|
931 | + $error .= $this->printShortUsage(true); |
|
932 | + throw new DeepExitException($error, 3); |
|
933 | + } |
|
934 | + |
|
935 | + $files = file($path); |
|
936 | + foreach ($files as $inputFile) { |
|
937 | + $inputFile = trim($inputFile); |
|
938 | + |
|
939 | + // Skip empty lines. |
|
940 | + if ($inputFile === '') { |
|
941 | + continue; |
|
942 | + } |
|
943 | + |
|
944 | + $this->processFilePath($inputFile); |
|
945 | + } |
|
946 | + } else if (substr($arg, 0, 11) === 'stdin-path=') { |
|
947 | + if (isset(self::$overriddenDefaults['stdinPath']) === true) { |
|
948 | + break; |
|
949 | + } |
|
950 | + |
|
951 | + $this->stdinPath = Util\Common::realpath(substr($arg, 11)); |
|
952 | + |
|
953 | + // It may not exist and return false instead, so use whatever they gave us. |
|
954 | + if ($this->stdinPath === false) { |
|
955 | + $this->stdinPath = trim(substr($arg, 11)); |
|
956 | + } |
|
957 | + |
|
958 | + self::$overriddenDefaults['stdinPath'] = true; |
|
959 | + } else if (PHP_CODESNIFFER_CBF === false && substr($arg, 0, 12) === 'report-file=') { |
|
960 | + if (isset(self::$overriddenDefaults['reportFile']) === true) { |
|
961 | + break; |
|
962 | + } |
|
963 | + |
|
964 | + $this->reportFile = Util\Common::realpath(substr($arg, 12)); |
|
965 | + |
|
966 | + // It may not exist and return false instead. |
|
967 | + if ($this->reportFile === false) { |
|
968 | + $this->reportFile = substr($arg, 12); |
|
969 | + |
|
970 | + $dir = dirname($this->reportFile); |
|
971 | + if (is_dir($dir) === false) { |
|
972 | + $error = 'ERROR: The specified report file path "'.$this->reportFile.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
973 | + $error .= $this->printShortUsage(true); |
|
974 | + throw new DeepExitException($error, 3); |
|
975 | + } |
|
976 | + |
|
977 | + if ($dir === '.') { |
|
978 | + // Passed report file is a file in the current directory. |
|
979 | + $this->reportFile = getcwd().'/'.basename($this->reportFile); |
|
980 | + } else { |
|
981 | + if ($dir{0} === '/') { |
|
982 | + // An absolute path. |
|
983 | + $dir = Util\Common::realpath($dir); |
|
984 | + } else { |
|
985 | + $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
986 | + } |
|
987 | + |
|
988 | + if ($dir !== false) { |
|
989 | + // Report file path is relative. |
|
990 | + $this->reportFile = $dir.'/'.basename($this->reportFile); |
|
991 | + } |
|
992 | + } |
|
993 | + }//end if |
|
994 | + |
|
995 | + self::$overriddenDefaults['reportFile'] = true; |
|
996 | + |
|
997 | + if (is_dir($this->reportFile) === true) { |
|
998 | + $error = 'ERROR: The specified report file path "'.$this->reportFile.'" is a directory'.PHP_EOL.PHP_EOL; |
|
999 | + $error .= $this->printShortUsage(true); |
|
1000 | + throw new DeepExitException($error, 3); |
|
1001 | + } |
|
1002 | + } else if (substr($arg, 0, 13) === 'report-width=') { |
|
1003 | + if (isset(self::$overriddenDefaults['reportWidth']) === true) { |
|
1004 | + break; |
|
1005 | + } |
|
1006 | + |
|
1007 | + $this->reportWidth = substr($arg, 13); |
|
1008 | + self::$overriddenDefaults['reportWidth'] = true; |
|
1009 | + } else if (substr($arg, 0, 9) === 'basepath=') { |
|
1010 | + if (isset(self::$overriddenDefaults['basepath']) === true) { |
|
1011 | + break; |
|
1012 | + } |
|
1013 | + |
|
1014 | + self::$overriddenDefaults['basepath'] = true; |
|
1015 | + |
|
1016 | + if (substr($arg, 9) === '') { |
|
1017 | + $this->basepath = null; |
|
1018 | + break; |
|
1019 | + } |
|
1020 | + |
|
1021 | + $this->basepath = Util\Common::realpath(substr($arg, 9)); |
|
1022 | + |
|
1023 | + // It may not exist and return false instead. |
|
1024 | + if ($this->basepath === false) { |
|
1025 | + $this->basepath = substr($arg, 9); |
|
1026 | + } |
|
1027 | + |
|
1028 | + if (is_dir($this->basepath) === false) { |
|
1029 | + $error = 'ERROR: The specified basepath "'.$this->basepath.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
1030 | + $error .= $this->printShortUsage(true); |
|
1031 | + throw new DeepExitException($error, 3); |
|
1032 | + } |
|
1033 | + } else if ((substr($arg, 0, 7) === 'report=' || substr($arg, 0, 7) === 'report-')) { |
|
1034 | + $reports = []; |
|
1035 | + |
|
1036 | + if ($arg[6] === '-') { |
|
1037 | + // This is a report with file output. |
|
1038 | + $split = strpos($arg, '='); |
|
1039 | + if ($split === false) { |
|
1040 | + $report = substr($arg, 7); |
|
1041 | + $output = null; |
|
1042 | + } else { |
|
1043 | + $report = substr($arg, 7, ($split - 7)); |
|
1044 | + $output = substr($arg, ($split + 1)); |
|
1045 | + if ($output === false) { |
|
1046 | + $output = null; |
|
1047 | + } else { |
|
1048 | + $dir = dirname($output); |
|
1049 | + if (is_dir($dir) === false) { |
|
1050 | + $error = 'ERROR: The specified '.$report.' report file path "'.$output.'" points to a non-existent directory'.PHP_EOL.PHP_EOL; |
|
1051 | + $error .= $this->printShortUsage(true); |
|
1052 | + throw new DeepExitException($error, 3); |
|
1053 | + } |
|
1054 | + |
|
1055 | + if ($dir === '.') { |
|
1056 | + // Passed report file is a filename in the current directory. |
|
1057 | + $output = getcwd().'/'.basename($output); |
|
1058 | + } else { |
|
1059 | + if ($dir{0} === '/') { |
|
1060 | + // An absolute path. |
|
1061 | + $dir = Util\Common::realpath($dir); |
|
1062 | + } else { |
|
1063 | + $dir = Util\Common::realpath(getcwd().'/'.$dir); |
|
1064 | + } |
|
1065 | + |
|
1066 | + if ($dir !== false) { |
|
1067 | + // Report file path is relative. |
|
1068 | + $output = $dir.'/'.basename($output); |
|
1069 | + } |
|
1070 | + } |
|
1071 | + }//end if |
|
1072 | + }//end if |
|
1073 | + |
|
1074 | + $reports[$report] = $output; |
|
1075 | + } else { |
|
1076 | + // This is a single report. |
|
1077 | + if (isset(self::$overriddenDefaults['reports']) === true) { |
|
1078 | + break; |
|
1079 | + } |
|
1080 | + |
|
1081 | + $reportNames = explode(',', substr($arg, 7)); |
|
1082 | + foreach ($reportNames as $report) { |
|
1083 | + $reports[$report] = null; |
|
1084 | + } |
|
1085 | + }//end if |
|
1086 | + |
|
1087 | + // Remove the default value so the CLI value overrides it. |
|
1088 | + if (isset(self::$overriddenDefaults['reports']) === false) { |
|
1089 | + $this->reports = $reports; |
|
1090 | + } else { |
|
1091 | + $this->reports = array_merge($this->reports, $reports); |
|
1092 | + } |
|
1093 | + |
|
1094 | + self::$overriddenDefaults['reports'] = true; |
|
1095 | + } else if (substr($arg, 0, 7) === 'filter=') { |
|
1096 | + if (isset(self::$overriddenDefaults['filter']) === true) { |
|
1097 | + break; |
|
1098 | + } |
|
1099 | + |
|
1100 | + $this->filter = substr($arg, 7); |
|
1101 | + self::$overriddenDefaults['filter'] = true; |
|
1102 | + } else if (substr($arg, 0, 9) === 'standard=') { |
|
1103 | + $standards = trim(substr($arg, 9)); |
|
1104 | + if ($standards !== '') { |
|
1105 | + $this->standards = explode(',', $standards); |
|
1106 | + } |
|
1107 | + |
|
1108 | + self::$overriddenDefaults['standards'] = true; |
|
1109 | + } else if (substr($arg, 0, 11) === 'extensions=') { |
|
1110 | + if (isset(self::$overriddenDefaults['extensions']) === true) { |
|
1111 | + break; |
|
1112 | + } |
|
1113 | + |
|
1114 | + $extensions = explode(',', substr($arg, 11)); |
|
1115 | + $newExtensions = []; |
|
1116 | + foreach ($extensions as $ext) { |
|
1117 | + $slash = strpos($ext, '/'); |
|
1118 | + if ($slash !== false) { |
|
1119 | + // They specified the tokenizer too. |
|
1120 | + list($ext, $tokenizer) = explode('/', $ext); |
|
1121 | + $newExtensions[$ext] = strtoupper($tokenizer); |
|
1122 | + continue; |
|
1123 | + } |
|
1124 | + |
|
1125 | + if (isset($this->extensions[$ext]) === true) { |
|
1126 | + $newExtensions[$ext] = $this->extensions[$ext]; |
|
1127 | + } else { |
|
1128 | + $newExtensions[$ext] = 'PHP'; |
|
1129 | + } |
|
1130 | + } |
|
1131 | + |
|
1132 | + $this->extensions = $newExtensions; |
|
1133 | + self::$overriddenDefaults['extensions'] = true; |
|
1134 | + } else if (substr($arg, 0, 7) === 'suffix=') { |
|
1135 | + if (isset(self::$overriddenDefaults['suffix']) === true) { |
|
1136 | + break; |
|
1137 | + } |
|
1138 | + |
|
1139 | + $this->suffix = substr($arg, 7); |
|
1140 | + self::$overriddenDefaults['suffix'] = true; |
|
1141 | + } else if (substr($arg, 0, 9) === 'parallel=') { |
|
1142 | + if (isset(self::$overriddenDefaults['parallel']) === true) { |
|
1143 | + break; |
|
1144 | + } |
|
1145 | + |
|
1146 | + $this->parallel = max((int) substr($arg, 9), 1); |
|
1147 | + self::$overriddenDefaults['parallel'] = true; |
|
1148 | + } else if (substr($arg, 0, 9) === 'severity=') { |
|
1149 | + $this->errorSeverity = (int) substr($arg, 9); |
|
1150 | + $this->warningSeverity = $this->errorSeverity; |
|
1151 | + if (isset(self::$overriddenDefaults['errorSeverity']) === false) { |
|
1152 | + self::$overriddenDefaults['errorSeverity'] = true; |
|
1153 | + } |
|
1154 | + |
|
1155 | + if (isset(self::$overriddenDefaults['warningSeverity']) === false) { |
|
1156 | + self::$overriddenDefaults['warningSeverity'] = true; |
|
1157 | + } |
|
1158 | + } else if (substr($arg, 0, 15) === 'error-severity=') { |
|
1159 | + if (isset(self::$overriddenDefaults['errorSeverity']) === true) { |
|
1160 | + break; |
|
1161 | + } |
|
1162 | + |
|
1163 | + $this->errorSeverity = (int) substr($arg, 15); |
|
1164 | + self::$overriddenDefaults['errorSeverity'] = true; |
|
1165 | + } else if (substr($arg, 0, 17) === 'warning-severity=') { |
|
1166 | + if (isset(self::$overriddenDefaults['warningSeverity']) === true) { |
|
1167 | + break; |
|
1168 | + } |
|
1169 | + |
|
1170 | + $this->warningSeverity = (int) substr($arg, 17); |
|
1171 | + self::$overriddenDefaults['warningSeverity'] = true; |
|
1172 | + } else if (substr($arg, 0, 7) === 'ignore=') { |
|
1173 | + if (isset(self::$overriddenDefaults['ignored']) === true) { |
|
1174 | + break; |
|
1175 | + } |
|
1176 | + |
|
1177 | + // Split the ignore string on commas, unless the comma is escaped |
|
1178 | + // using 1 or 3 slashes (\, or \\\,). |
|
1179 | + $patterns = preg_split( |
|
1180 | + '/(?<=(?<!\\\\)\\\\\\\\),|(?<!\\\\),/', |
|
1181 | + substr($arg, 7) |
|
1182 | + ); |
|
1183 | + |
|
1184 | + $ignored = []; |
|
1185 | + foreach ($patterns as $pattern) { |
|
1186 | + $pattern = trim($pattern); |
|
1187 | + if ($pattern === '') { |
|
1188 | + continue; |
|
1189 | + } |
|
1190 | + |
|
1191 | + $ignored[$pattern] = 'absolute'; |
|
1192 | + } |
|
1193 | + |
|
1194 | + $this->ignored = $ignored; |
|
1195 | + self::$overriddenDefaults['ignored'] = true; |
|
1196 | + } else if (substr($arg, 0, 10) === 'generator=' |
|
1197 | + && PHP_CODESNIFFER_CBF === false |
|
1198 | + ) { |
|
1199 | + if (isset(self::$overriddenDefaults['generator']) === true) { |
|
1200 | + break; |
|
1201 | + } |
|
1202 | + |
|
1203 | + $this->generator = substr($arg, 10); |
|
1204 | + self::$overriddenDefaults['generator'] = true; |
|
1205 | + } else if (substr($arg, 0, 9) === 'encoding=') { |
|
1206 | + if (isset(self::$overriddenDefaults['encoding']) === true) { |
|
1207 | + break; |
|
1208 | + } |
|
1209 | + |
|
1210 | + $this->encoding = strtolower(substr($arg, 9)); |
|
1211 | + self::$overriddenDefaults['encoding'] = true; |
|
1212 | + } else if (substr($arg, 0, 10) === 'tab-width=') { |
|
1213 | + if (isset(self::$overriddenDefaults['tabWidth']) === true) { |
|
1214 | + break; |
|
1215 | + } |
|
1216 | + |
|
1217 | + $this->tabWidth = (int) substr($arg, 10); |
|
1218 | + self::$overriddenDefaults['tabWidth'] = true; |
|
1219 | + } else { |
|
1220 | + if ($this->dieOnUnknownArg === false) { |
|
1221 | + $eqPos = strpos($arg, '='); |
|
1222 | + try { |
|
1223 | + if ($eqPos === false) { |
|
1224 | + $this->values[$arg] = $arg; |
|
1225 | + } else { |
|
1226 | + $value = substr($arg, ($eqPos + 1)); |
|
1227 | + $arg = substr($arg, 0, $eqPos); |
|
1228 | + $this->values[$arg] = $value; |
|
1229 | + } |
|
1230 | + } catch (RuntimeException $e) { |
|
1231 | + // Value is not valid, so just ignore it. |
|
1232 | + } |
|
1233 | + } else { |
|
1234 | + $this->processUnknownArgument('--'.$arg, $pos); |
|
1235 | + } |
|
1236 | + }//end if |
|
1237 | + break; |
|
1238 | + }//end switch |
|
1239 | + |
|
1240 | + }//end processLongArgument() |
|
1241 | + |
|
1242 | + |
|
1243 | + /** |
|
1244 | + * Processes an unknown command line argument. |
|
1245 | + * |
|
1246 | + * Assumes all unknown arguments are files and folders to check. |
|
1247 | + * |
|
1248 | + * @param string $arg The command line argument. |
|
1249 | + * @param int $pos The position of the argument on the command line. |
|
1250 | + * |
|
1251 | + * @return void |
|
1252 | + */ |
|
1253 | + public function processUnknownArgument($arg, $pos) |
|
1254 | + { |
|
1255 | + // We don't know about any additional switches; just files. |
|
1256 | + if ($arg{0} === '-') { |
|
1257 | + if ($this->dieOnUnknownArg === false) { |
|
1258 | + return; |
|
1259 | + } |
|
1260 | + |
|
1261 | + $error = "ERROR: option \"$arg\" not known".PHP_EOL.PHP_EOL; |
|
1262 | + $error .= $this->printShortUsage(true); |
|
1263 | + throw new DeepExitException($error, 3); |
|
1264 | + } |
|
1265 | + |
|
1266 | + $this->processFilePath($arg); |
|
1267 | + |
|
1268 | + }//end processUnknownArgument() |
|
1269 | + |
|
1270 | + |
|
1271 | + /** |
|
1272 | + * Processes a file path and add it to the file list. |
|
1273 | + * |
|
1274 | + * @param string $path The path to the file to add. |
|
1275 | + * |
|
1276 | + * @return void |
|
1277 | + */ |
|
1278 | + public function processFilePath($path) |
|
1279 | + { |
|
1280 | + // If we are processing STDIN, don't record any files to check. |
|
1281 | + if ($this->stdin === true) { |
|
1282 | + return; |
|
1283 | + } |
|
1284 | + |
|
1285 | + $file = Util\Common::realpath($path); |
|
1286 | + if (file_exists($file) === false) { |
|
1287 | + if ($this->dieOnUnknownArg === false) { |
|
1288 | + return; |
|
1289 | + } |
|
1290 | + |
|
1291 | + $error = 'ERROR: The file "'.$path.'" does not exist.'.PHP_EOL.PHP_EOL; |
|
1292 | + $error .= $this->printShortUsage(true); |
|
1293 | + throw new DeepExitException($error, 3); |
|
1294 | + } else { |
|
1295 | + // Can't modify the files array directly because it's not a real |
|
1296 | + // class member, so need to use this little get/modify/set trick. |
|
1297 | + $files = $this->files; |
|
1298 | + $files[] = $file; |
|
1299 | + $this->files = $files; |
|
1300 | + self::$overriddenDefaults['files'] = true; |
|
1301 | + } |
|
1302 | + |
|
1303 | + }//end processFilePath() |
|
1304 | + |
|
1305 | + |
|
1306 | + /** |
|
1307 | + * Prints out the usage information for this script. |
|
1308 | + * |
|
1309 | + * @return void |
|
1310 | + */ |
|
1311 | + public function printUsage() |
|
1312 | + { |
|
1313 | + echo PHP_EOL; |
|
1314 | + |
|
1315 | + if (PHP_CODESNIFFER_CBF === true) { |
|
1316 | + $this->printPHPCBFUsage(); |
|
1317 | + } else { |
|
1318 | + $this->printPHPCSUsage(); |
|
1319 | + } |
|
1320 | + |
|
1321 | + echo PHP_EOL; |
|
1322 | + |
|
1323 | + }//end printUsage() |
|
1324 | + |
|
1325 | + |
|
1326 | + /** |
|
1327 | + * Prints out the short usage information for this script. |
|
1328 | + * |
|
1329 | + * @param bool $return If TRUE, the usage string is returned |
|
1330 | + * instead of output to screen. |
|
1331 | + * |
|
1332 | + * @return string|void |
|
1333 | + */ |
|
1334 | + public function printShortUsage($return=false) |
|
1335 | + { |
|
1336 | + if (PHP_CODESNIFFER_CBF === true) { |
|
1337 | + $usage = 'Run "phpcbf --help" for usage information'; |
|
1338 | + } else { |
|
1339 | + $usage = 'Run "phpcs --help" for usage information'; |
|
1340 | + } |
|
1341 | + |
|
1342 | + $usage .= PHP_EOL.PHP_EOL; |
|
1343 | + |
|
1344 | + if ($return === true) { |
|
1345 | + return $usage; |
|
1346 | + } |
|
1347 | + |
|
1348 | + echo $usage; |
|
1349 | + |
|
1350 | + }//end printShortUsage() |
|
1351 | + |
|
1352 | + |
|
1353 | + /** |
|
1354 | + * Prints out the usage information for PHPCS. |
|
1355 | + * |
|
1356 | + * @return void |
|
1357 | + */ |
|
1358 | + public function printPHPCSUsage() |
|
1359 | + { |
|
1360 | + echo 'Usage: phpcs [-nwlsaepqvi] [-d key[=value]] [--colors] [--no-colors]'.PHP_EOL; |
|
1361 | + echo ' [--cache[=<cacheFile>]] [--no-cache] [--tab-width=<tabWidth>]'.PHP_EOL; |
|
1362 | + echo ' [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>]'.PHP_EOL; |
|
1363 | + echo ' [--report-width=<reportWidth>] [--basepath=<basepath>] [--bootstrap=<bootstrap>]'.PHP_EOL; |
|
1364 | + echo ' [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL; |
|
1365 | + echo ' [--runtime-set key value] [--config-set key value] [--config-delete key] [--config-show]'.PHP_EOL; |
|
1366 | + echo ' [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>]'.PHP_EOL; |
|
1367 | + echo ' [--encoding=<encoding>] [--parallel=<processes>] [--generator=<generator>]'.PHP_EOL; |
|
1368 | + echo ' [--extensions=<extensions>] [--ignore=<patterns>] [--ignore-annotations]'.PHP_EOL; |
|
1369 | + echo ' [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL; |
|
1370 | + echo PHP_EOL; |
|
1371 | + echo ' - Check STDIN instead of local files and directories'.PHP_EOL; |
|
1372 | + echo ' -n Do not print warnings (shortcut for --warning-severity=0)'.PHP_EOL; |
|
1373 | + echo ' -w Print both warnings and errors (this is the default)'.PHP_EOL; |
|
1374 | + echo ' -l Local directory only, no recursion'.PHP_EOL; |
|
1375 | + echo ' -s Show sniff codes in all reports'.PHP_EOL; |
|
1376 | + echo ' -a Run interactively'.PHP_EOL; |
|
1377 | + echo ' -e Explain a standard by showing the sniffs it includes'.PHP_EOL; |
|
1378 | + echo ' -p Show progress of the run'.PHP_EOL; |
|
1379 | + echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; |
|
1380 | + echo ' -m Stop error messages from being recorded'.PHP_EOL; |
|
1381 | + echo ' (saves a lot of memory, but stops many reports from being used)'.PHP_EOL; |
|
1382 | + echo ' -v Print processed files'.PHP_EOL; |
|
1383 | + echo ' -vv Print ruleset and token output'.PHP_EOL; |
|
1384 | + echo ' -vvv Print sniff processing information'.PHP_EOL; |
|
1385 | + echo ' -i Show a list of installed coding standards'.PHP_EOL; |
|
1386 | + echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; |
|
1387 | + echo PHP_EOL; |
|
1388 | + echo ' --help Print this help message'.PHP_EOL; |
|
1389 | + echo ' --version Print version information'.PHP_EOL; |
|
1390 | + echo ' --colors Use colors in output'.PHP_EOL; |
|
1391 | + echo ' --no-colors Do not use colors in output (this is the default)'.PHP_EOL; |
|
1392 | + echo ' --cache Cache results between runs'.PHP_EOL; |
|
1393 | + echo ' --no-cache Do not cache results between runs (this is the default)'.PHP_EOL; |
|
1394 | + echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; |
|
1395 | + echo PHP_EOL; |
|
1396 | + echo ' <cacheFile> Use a specific file for caching (uses a temporary file by default)'.PHP_EOL; |
|
1397 | + echo ' <basepath> A path to strip from the front of file paths inside reports'.PHP_EOL; |
|
1398 | + echo ' <bootstrap> A comma separated list of files to run before processing begins'.PHP_EOL; |
|
1399 | + echo ' <encoding> The encoding of the files being checked (default is utf-8)'.PHP_EOL; |
|
1400 | + echo ' <extensions> A comma separated list of file extensions to check'.PHP_EOL; |
|
1401 | + echo ' The type of the file can be specified using: ext/type'.PHP_EOL; |
|
1402 | + echo ' e.g., module/php,es/js'.PHP_EOL; |
|
1403 | + echo ' <file> One or more files and/or directories to check'.PHP_EOL; |
|
1404 | + echo ' <fileList> A file containing a list of files and/or directories to check (one per line)'.PHP_EOL; |
|
1405 | + echo ' <filter> Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; |
|
1406 | + echo ' <generator> Uses either the "HTML", "Markdown" or "Text" generator'.PHP_EOL; |
|
1407 | + echo ' (forces documentation generation instead of checking)'.PHP_EOL; |
|
1408 | + echo ' <patterns> A comma separated list of patterns to ignore files and directories'.PHP_EOL; |
|
1409 | + echo ' <processes> How many files should be checked simultaneously (default is 1)'.PHP_EOL; |
|
1410 | + echo ' <report> Print either the "full", "xml", "checkstyle", "csv"'.PHP_EOL; |
|
1411 | + echo ' "json", "junit", "emacs", "source", "summary", "diff"'.PHP_EOL; |
|
1412 | + echo ' "svnblame", "gitblame", "hgblame" or "notifysend" report,'.PHP_EOL; |
|
1413 | + echo ' or specify the path to a custom report class'.PHP_EOL; |
|
1414 | + echo ' (the "full" report is printed by default)'.PHP_EOL; |
|
1415 | + echo ' <reportFile> Write the report to the specified file path'.PHP_EOL; |
|
1416 | + echo ' <reportWidth> How many columns wide screen reports should be printed'.PHP_EOL; |
|
1417 | + echo ' or set to "auto" to use current screen width, where supported'.PHP_EOL; |
|
1418 | + echo ' <severity> The minimum severity required to display an error or warning'.PHP_EOL; |
|
1419 | + echo ' <sniffs> A comma separated list of sniff codes to include or exclude from checking'.PHP_EOL; |
|
1420 | + echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; |
|
1421 | + echo ' <standard> The name or path of the coding standard to use'.PHP_EOL; |
|
1422 | + echo ' <stdinPath> If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; |
|
1423 | + echo ' <tabWidth> The number of spaces each tab represents'.PHP_EOL; |
|
1424 | + |
|
1425 | + }//end printPHPCSUsage() |
|
1426 | + |
|
1427 | + |
|
1428 | + /** |
|
1429 | + * Prints out the usage information for PHPCBF. |
|
1430 | + * |
|
1431 | + * @return void |
|
1432 | + */ |
|
1433 | + public function printPHPCBFUsage() |
|
1434 | + { |
|
1435 | + echo 'Usage: phpcbf [-nwli] [-d key[=value]] [--ignore-annotations] [--bootstrap=<bootstrap>]'.PHP_EOL; |
|
1436 | + echo ' [--standard=<standard>] [--sniffs=<sniffs>] [--exclude=<sniffs>] [--suffix=<suffix>]'.PHP_EOL; |
|
1437 | + echo ' [--severity=<severity>] [--error-severity=<severity>] [--warning-severity=<severity>]'.PHP_EOL; |
|
1438 | + echo ' [--tab-width=<tabWidth>] [--encoding=<encoding>] [--parallel=<processes>]'.PHP_EOL; |
|
1439 | + echo ' [--basepath=<basepath>] [--extensions=<extensions>] [--ignore=<patterns>]'.PHP_EOL; |
|
1440 | + echo ' [--stdin-path=<stdinPath>] [--file-list=<fileList>] [--filter=<filter>] <file> - ...'.PHP_EOL; |
|
1441 | + echo PHP_EOL; |
|
1442 | + echo ' - Fix STDIN instead of local files and directories'.PHP_EOL; |
|
1443 | + echo ' -n Do not fix warnings (shortcut for --warning-severity=0)'.PHP_EOL; |
|
1444 | + echo ' -w Fix both warnings and errors (on by default)'.PHP_EOL; |
|
1445 | + echo ' -l Local directory only, no recursion'.PHP_EOL; |
|
1446 | + echo ' -p Show progress of the run'.PHP_EOL; |
|
1447 | + echo ' -q Quiet mode; disables progress and verbose output'.PHP_EOL; |
|
1448 | + echo ' -v Print processed files'.PHP_EOL; |
|
1449 | + echo ' -vv Print ruleset and token output'.PHP_EOL; |
|
1450 | + echo ' -vvv Print sniff processing information'.PHP_EOL; |
|
1451 | + echo ' -i Show a list of installed coding standards'.PHP_EOL; |
|
1452 | + echo ' -d Set the [key] php.ini value to [value] or [true] if value is omitted'.PHP_EOL; |
|
1453 | + echo PHP_EOL; |
|
1454 | + echo ' --help Print this help message'.PHP_EOL; |
|
1455 | + echo ' --version Print version information'.PHP_EOL; |
|
1456 | + echo ' --ignore-annotations Ignore all phpcs: annotations in code comments'.PHP_EOL; |
|
1457 | + echo PHP_EOL; |
|
1458 | + echo ' <basepath> A path to strip from the front of file paths inside reports'.PHP_EOL; |
|
1459 | + echo ' <bootstrap> A comma separated list of files to run before processing begins'.PHP_EOL; |
|
1460 | + echo ' <encoding> The encoding of the files being fixed (default is utf-8)'.PHP_EOL; |
|
1461 | + echo ' <extensions> A comma separated list of file extensions to fix'.PHP_EOL; |
|
1462 | + echo ' The type of the file can be specified using: ext/type'.PHP_EOL; |
|
1463 | + echo ' e.g., module/php,es/js'.PHP_EOL; |
|
1464 | + echo ' <file> One or more files and/or directories to fix'.PHP_EOL; |
|
1465 | + echo ' <fileList> A file containing a list of files and/or directories to fix (one per line)'.PHP_EOL; |
|
1466 | + echo ' <filter> Use the "gitmodified" filter, or specify the path to a custom filter class'.PHP_EOL; |
|
1467 | + echo ' <patterns> A comma separated list of patterns to ignore files and directories'.PHP_EOL; |
|
1468 | + echo ' <processes> How many files should be fixed simultaneously (default is 1)'.PHP_EOL; |
|
1469 | + echo ' <severity> The minimum severity required to fix an error or warning'.PHP_EOL; |
|
1470 | + echo ' <sniffs> A comma separated list of sniff codes to include or exclude from fixing'.PHP_EOL; |
|
1471 | + echo ' (all sniffs must be part of the specified standard)'.PHP_EOL; |
|
1472 | + echo ' <standard> The name or path of the coding standard to use'.PHP_EOL; |
|
1473 | + echo ' <stdinPath> If processing STDIN, the file path that STDIN will be processed as'.PHP_EOL; |
|
1474 | + echo ' <suffix> Write modified files to a filename using this suffix'.PHP_EOL; |
|
1475 | + echo ' ("diff" and "patch" are not used in this mode)'.PHP_EOL; |
|
1476 | + echo ' <tabWidth> The number of spaces each tab represents'.PHP_EOL; |
|
1477 | + |
|
1478 | + }//end printPHPCBFUsage() |
|
1479 | + |
|
1480 | + |
|
1481 | + /** |
|
1482 | + * Get a single config value. |
|
1483 | + * |
|
1484 | + * @param string $key The name of the config value. |
|
1485 | + * |
|
1486 | + * @return string|null |
|
1487 | + * @see setConfigData() |
|
1488 | + * @see getAllConfigData() |
|
1489 | + */ |
|
1490 | + public static function getConfigData($key) |
|
1491 | + { |
|
1492 | + $phpCodeSnifferConfig = self::getAllConfigData(); |
|
1493 | + |
|
1494 | + if ($phpCodeSnifferConfig === null) { |
|
1495 | + return null; |
|
1496 | + } |
|
1497 | + |
|
1498 | + if (isset($phpCodeSnifferConfig[$key]) === false) { |
|
1499 | + return null; |
|
1500 | + } |
|
1501 | + |
|
1502 | + return $phpCodeSnifferConfig[$key]; |
|
1503 | + |
|
1504 | + }//end getConfigData() |
|
1505 | + |
|
1506 | + |
|
1507 | + /** |
|
1508 | + * Get the path to an executable utility. |
|
1509 | + * |
|
1510 | + * @param string $name The name of the executable utility. |
|
1511 | + * |
|
1512 | + * @return string|null |
|
1513 | + * @see getConfigData() |
|
1514 | + */ |
|
1515 | + public static function getExecutablePath($name) |
|
1516 | + { |
|
1517 | + $data = self::getConfigData($name.'_path'); |
|
1518 | + if ($data !== null) { |
|
1519 | + return $data; |
|
1520 | + } |
|
1521 | + |
|
1522 | + if ($name === "php") { |
|
1523 | + // For php, we know the executable path. There's no need to look it up. |
|
1524 | + return PHP_BINARY; |
|
1525 | + } |
|
1526 | + |
|
1527 | + if (array_key_exists($name, self::$executablePaths) === true) { |
|
1528 | + return self::$executablePaths[$name]; |
|
1529 | + } |
|
1530 | + |
|
1531 | + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
|
1532 | + $cmd = 'where '.escapeshellarg($name).' 2> nul'; |
|
1533 | + } else { |
|
1534 | + $cmd = 'which '.escapeshellarg($name).' 2> /dev/null'; |
|
1535 | + } |
|
1536 | + |
|
1537 | + $result = exec($cmd, $output, $retVal); |
|
1538 | + if ($retVal !== 0) { |
|
1539 | + $result = null; |
|
1540 | + } |
|
1541 | + |
|
1542 | + self::$executablePaths[$name] = $result; |
|
1543 | + return $result; |
|
1544 | + |
|
1545 | + }//end getExecutablePath() |
|
1546 | + |
|
1547 | + |
|
1548 | + /** |
|
1549 | + * Set a single config value. |
|
1550 | + * |
|
1551 | + * @param string $key The name of the config value. |
|
1552 | + * @param string|null $value The value to set. If null, the config |
|
1553 | + * entry is deleted, reverting it to the |
|
1554 | + * default value. |
|
1555 | + * @param boolean $temp Set this config data temporarily for this |
|
1556 | + * script run. This will not write the config |
|
1557 | + * data to the config file. |
|
1558 | + * |
|
1559 | + * @return bool |
|
1560 | + * @see getConfigData() |
|
1561 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the config file can not be written. |
|
1562 | + */ |
|
1563 | + public static function setConfigData($key, $value, $temp=false) |
|
1564 | + { |
|
1565 | + if (isset(self::$overriddenDefaults['runtime-set']) === true |
|
1566 | + && isset(self::$overriddenDefaults['runtime-set'][$key]) === true |
|
1567 | + ) { |
|
1568 | + return false; |
|
1569 | + } |
|
1570 | + |
|
1571 | + if ($temp === false) { |
|
1572 | + $path = ''; |
|
1573 | + if (is_callable('\Phar::running') === true) { |
|
1574 | + $path = \Phar::running(false); |
|
1575 | + } |
|
1576 | + |
|
1577 | + if ($path !== '') { |
|
1578 | + $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1579 | + } else { |
|
1580 | + $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1581 | + if (is_file($configFile) === false |
|
1582 | + && strpos('@data_dir@', '@data_dir') === false |
|
1583 | + ) { |
|
1584 | + // If data_dir was replaced, this is a PEAR install and we can |
|
1585 | + // use the PEAR data dir to store the conf file. |
|
1586 | + $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; |
|
1587 | + } |
|
1588 | + } |
|
1589 | + |
|
1590 | + if (is_file($configFile) === true |
|
1591 | + && is_writable($configFile) === false |
|
1592 | + ) { |
|
1593 | + $error = 'ERROR: Config file '.$configFile.' is not writable'.PHP_EOL.PHP_EOL; |
|
1594 | + throw new DeepExitException($error, 3); |
|
1595 | + } |
|
1596 | + }//end if |
|
1597 | + |
|
1598 | + $phpCodeSnifferConfig = self::getAllConfigData(); |
|
1599 | + |
|
1600 | + if ($value === null) { |
|
1601 | + if (isset($phpCodeSnifferConfig[$key]) === true) { |
|
1602 | + unset($phpCodeSnifferConfig[$key]); |
|
1603 | + } |
|
1604 | + } else { |
|
1605 | + $phpCodeSnifferConfig[$key] = $value; |
|
1606 | + } |
|
1607 | + |
|
1608 | + if ($temp === false) { |
|
1609 | + $output = '<'.'?php'."\n".' $phpCodeSnifferConfig = '; |
|
1610 | + $output .= var_export($phpCodeSnifferConfig, true); |
|
1611 | + $output .= "\n?".'>'; |
|
1612 | + |
|
1613 | + if (file_put_contents($configFile, $output) === false) { |
|
1614 | + $error = 'ERROR: Config file '.$configFile.' could not be written'.PHP_EOL.PHP_EOL; |
|
1615 | + throw new DeepExitException($error, 3); |
|
1616 | + } |
|
1617 | + |
|
1618 | + self::$configDataFile = $configFile; |
|
1619 | + } |
|
1620 | + |
|
1621 | + self::$configData = $phpCodeSnifferConfig; |
|
1622 | + |
|
1623 | + // If the installed paths are being set, make sure all known |
|
1624 | + // standards paths are added to the autoloader. |
|
1625 | + if ($key === 'installed_paths') { |
|
1626 | + $installedStandards = Util\Standards::getInstalledStandardDetails(); |
|
1627 | + foreach ($installedStandards as $name => $details) { |
|
1628 | + Autoload::addSearchPath($details['path'], $details['namespace']); |
|
1629 | + } |
|
1630 | + } |
|
1631 | + |
|
1632 | + return true; |
|
1633 | + |
|
1634 | + }//end setConfigData() |
|
1635 | + |
|
1636 | + |
|
1637 | + /** |
|
1638 | + * Get all config data. |
|
1639 | + * |
|
1640 | + * @return array<string, string> |
|
1641 | + * @see getConfigData() |
|
1642 | + */ |
|
1643 | + public static function getAllConfigData() |
|
1644 | + { |
|
1645 | + if (self::$configData !== null) { |
|
1646 | + return self::$configData; |
|
1647 | + } |
|
1648 | + |
|
1649 | + $path = ''; |
|
1650 | + if (is_callable('\Phar::running') === true) { |
|
1651 | + $path = \Phar::running(false); |
|
1652 | + } |
|
1653 | + |
|
1654 | + if ($path !== '') { |
|
1655 | + $configFile = dirname($path).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1656 | + } else { |
|
1657 | + $configFile = dirname(__DIR__).DIRECTORY_SEPARATOR.'CodeSniffer.conf'; |
|
1658 | + if (is_file($configFile) === false |
|
1659 | + && strpos('@data_dir@', '@data_dir') === false |
|
1660 | + ) { |
|
1661 | + $configFile = '@data_dir@/PHP_CodeSniffer/CodeSniffer.conf'; |
|
1662 | + } |
|
1663 | + } |
|
1664 | + |
|
1665 | + if (is_file($configFile) === false) { |
|
1666 | + self::$configData = []; |
|
1667 | + return []; |
|
1668 | + } |
|
1669 | + |
|
1670 | + if (is_readable($configFile) === false) { |
|
1671 | + $error = 'ERROR: Config file '.$configFile.' is not readable'.PHP_EOL.PHP_EOL; |
|
1672 | + throw new DeepExitException($error, 3); |
|
1673 | + } |
|
1674 | + |
|
1675 | + include $configFile; |
|
1676 | + self::$configDataFile = $configFile; |
|
1677 | + self::$configData = $phpCodeSnifferConfig; |
|
1678 | + return self::$configData; |
|
1679 | + |
|
1680 | + }//end getAllConfigData() |
|
1681 | + |
|
1682 | + |
|
1683 | + /** |
|
1684 | + * Prints out the gathered config data. |
|
1685 | + * |
|
1686 | + * @param array $data The config data to print. |
|
1687 | + * |
|
1688 | + * @return void |
|
1689 | + */ |
|
1690 | + public function printConfigData($data) |
|
1691 | + { |
|
1692 | + $max = 0; |
|
1693 | + $keys = array_keys($data); |
|
1694 | + foreach ($keys as $key) { |
|
1695 | + $len = strlen($key); |
|
1696 | + if (strlen($key) > $max) { |
|
1697 | + $max = $len; |
|
1698 | + } |
|
1699 | + } |
|
1700 | + |
|
1701 | + if ($max === 0) { |
|
1702 | + return; |
|
1703 | + } |
|
1704 | + |
|
1705 | + $max += 2; |
|
1706 | + ksort($data); |
|
1707 | + foreach ($data as $name => $value) { |
|
1708 | + echo str_pad($name.': ', $max).$value.PHP_EOL; |
|
1709 | + } |
|
1710 | + |
|
1711 | + }//end printConfigData() |
|
1712 | 1712 | |
1713 | 1713 | |
1714 | 1714 | }//end class |
@@ -20,61 +20,61 @@ |
||
20 | 20 | { |
21 | 21 | |
22 | 22 | |
23 | - /** |
|
24 | - * Registers the tokens that this sniff wants to listen for. |
|
25 | - * |
|
26 | - * An example return value for a sniff that wants to listen for whitespace |
|
27 | - * and any comments would be: |
|
28 | - * |
|
29 | - * <code> |
|
30 | - * return array( |
|
31 | - * T_WHITESPACE, |
|
32 | - * T_DOC_COMMENT, |
|
33 | - * T_COMMENT, |
|
34 | - * ); |
|
35 | - * </code> |
|
36 | - * |
|
37 | - * @return mixed[] |
|
38 | - * @see Tokens.php |
|
39 | - */ |
|
40 | - public function register(); |
|
23 | + /** |
|
24 | + * Registers the tokens that this sniff wants to listen for. |
|
25 | + * |
|
26 | + * An example return value for a sniff that wants to listen for whitespace |
|
27 | + * and any comments would be: |
|
28 | + * |
|
29 | + * <code> |
|
30 | + * return array( |
|
31 | + * T_WHITESPACE, |
|
32 | + * T_DOC_COMMENT, |
|
33 | + * T_COMMENT, |
|
34 | + * ); |
|
35 | + * </code> |
|
36 | + * |
|
37 | + * @return mixed[] |
|
38 | + * @see Tokens.php |
|
39 | + */ |
|
40 | + public function register(); |
|
41 | 41 | |
42 | 42 | |
43 | - /** |
|
44 | - * Called when one of the token types that this sniff is listening for |
|
45 | - * is found. |
|
46 | - * |
|
47 | - * The stackPtr variable indicates where in the stack the token was found. |
|
48 | - * A sniff can acquire information this token, along with all the other |
|
49 | - * tokens within the stack by first acquiring the token stack: |
|
50 | - * |
|
51 | - * <code> |
|
52 | - * $tokens = $phpcsFile->getTokens(); |
|
53 | - * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token'; |
|
54 | - * echo 'token information: '; |
|
55 | - * print_r($tokens[$stackPtr]); |
|
56 | - * </code> |
|
57 | - * |
|
58 | - * If the sniff discovers an anomaly in the code, they can raise an error |
|
59 | - * by calling addError() on the \PHP_CodeSniffer\Files\File object, specifying an error |
|
60 | - * message and the position of the offending token: |
|
61 | - * |
|
62 | - * <code> |
|
63 | - * $phpcsFile->addError('Encountered an error', $stackPtr); |
|
64 | - * </code> |
|
65 | - * |
|
66 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
67 | - * token was found. |
|
68 | - * @param int $stackPtr The position in the PHP_CodeSniffer |
|
69 | - * file's token stack where the token |
|
70 | - * was found. |
|
71 | - * |
|
72 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
73 | - * called again on the current file until the returned stack |
|
74 | - * pointer is reached. Return (count($tokens) + 1) to skip |
|
75 | - * the rest of the file. |
|
76 | - */ |
|
77 | - public function process(File $phpcsFile, $stackPtr); |
|
43 | + /** |
|
44 | + * Called when one of the token types that this sniff is listening for |
|
45 | + * is found. |
|
46 | + * |
|
47 | + * The stackPtr variable indicates where in the stack the token was found. |
|
48 | + * A sniff can acquire information this token, along with all the other |
|
49 | + * tokens within the stack by first acquiring the token stack: |
|
50 | + * |
|
51 | + * <code> |
|
52 | + * $tokens = $phpcsFile->getTokens(); |
|
53 | + * echo 'Encountered a '.$tokens[$stackPtr]['type'].' token'; |
|
54 | + * echo 'token information: '; |
|
55 | + * print_r($tokens[$stackPtr]); |
|
56 | + * </code> |
|
57 | + * |
|
58 | + * If the sniff discovers an anomaly in the code, they can raise an error |
|
59 | + * by calling addError() on the \PHP_CodeSniffer\Files\File object, specifying an error |
|
60 | + * message and the position of the offending token: |
|
61 | + * |
|
62 | + * <code> |
|
63 | + * $phpcsFile->addError('Encountered an error', $stackPtr); |
|
64 | + * </code> |
|
65 | + * |
|
66 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
67 | + * token was found. |
|
68 | + * @param int $stackPtr The position in the PHP_CodeSniffer |
|
69 | + * file's token stack where the token |
|
70 | + * was found. |
|
71 | + * |
|
72 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
73 | + * called again on the current file until the returned stack |
|
74 | + * pointer is reached. Return (count($tokens) + 1) to skip |
|
75 | + * the rest of the file. |
|
76 | + */ |
|
77 | + public function process(File $phpcsFile, $stackPtr); |
|
78 | 78 | |
79 | 79 | |
80 | 80 | }//end interface |
@@ -32,160 +32,160 @@ |
||
32 | 32 | abstract class AbstractScopeSniff implements Sniff |
33 | 33 | { |
34 | 34 | |
35 | - /** |
|
36 | - * The token types that this test wishes to listen to within the scope. |
|
37 | - * |
|
38 | - * @var array |
|
39 | - */ |
|
40 | - private $tokens = []; |
|
41 | - |
|
42 | - /** |
|
43 | - * The type of scope opener tokens that this test wishes to listen to. |
|
44 | - * |
|
45 | - * @var string |
|
46 | - */ |
|
47 | - private $scopeTokens = []; |
|
48 | - |
|
49 | - /** |
|
50 | - * True if this test should fire on tokens outside of the scope. |
|
51 | - * |
|
52 | - * @var boolean |
|
53 | - */ |
|
54 | - private $listenOutside = false; |
|
55 | - |
|
56 | - |
|
57 | - /** |
|
58 | - * Constructs a new AbstractScopeTest. |
|
59 | - * |
|
60 | - * @param array $scopeTokens The type of scope the test wishes to listen to. |
|
61 | - * @param array $tokens The tokens that the test wishes to listen to |
|
62 | - * within the scope. |
|
63 | - * @param boolean $listenOutside If true this test will also alert the |
|
64 | - * extending class when a token is found outside |
|
65 | - * the scope, by calling the |
|
66 | - * processTokenOutsideScope method. |
|
67 | - * |
|
68 | - * @see PHP_CodeSniffer.getValidScopeTokeners() |
|
69 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens array is empty. |
|
70 | - */ |
|
71 | - public function __construct( |
|
72 | - array $scopeTokens, |
|
73 | - array $tokens, |
|
74 | - $listenOutside=false |
|
75 | - ) { |
|
76 | - if (empty($scopeTokens) === true) { |
|
77 | - $error = 'The scope tokens list cannot be empty'; |
|
78 | - throw new RuntimeException($error); |
|
79 | - } |
|
80 | - |
|
81 | - if (empty($tokens) === true) { |
|
82 | - $error = 'The tokens list cannot be empty'; |
|
83 | - throw new RuntimeException($error); |
|
84 | - } |
|
85 | - |
|
86 | - $invalidScopeTokens = array_intersect($scopeTokens, $tokens); |
|
87 | - if (empty($invalidScopeTokens) === false) { |
|
88 | - $invalid = implode(', ', $invalidScopeTokens); |
|
89 | - $error = "Scope tokens [$invalid] can't be in the tokens array"; |
|
90 | - throw new RuntimeException($error); |
|
91 | - } |
|
92 | - |
|
93 | - $this->listenOutside = $listenOutside; |
|
94 | - $this->scopeTokens = array_flip($scopeTokens); |
|
95 | - $this->tokens = $tokens; |
|
96 | - |
|
97 | - }//end __construct() |
|
98 | - |
|
99 | - |
|
100 | - /** |
|
101 | - * The method that is called to register the tokens this test wishes to |
|
102 | - * listen to. |
|
103 | - * |
|
104 | - * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register |
|
105 | - * for the desired tokens and scope. |
|
106 | - * |
|
107 | - * @return int[] |
|
108 | - * @see __constructor() |
|
109 | - */ |
|
110 | - final public function register() |
|
111 | - { |
|
112 | - return $this->tokens; |
|
113 | - |
|
114 | - }//end register() |
|
115 | - |
|
116 | - |
|
117 | - /** |
|
118 | - * Processes the tokens that this test is listening for. |
|
119 | - * |
|
120 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
121 | - * @param int $stackPtr The position in the stack where this |
|
122 | - * token was found. |
|
123 | - * |
|
124 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
125 | - * called again on the current file until the returned stack |
|
126 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
127 | - * the rest of the file. |
|
128 | - * @see processTokenWithinScope() |
|
129 | - */ |
|
130 | - final public function process(File $phpcsFile, $stackPtr) |
|
131 | - { |
|
132 | - $tokens = $phpcsFile->getTokens(); |
|
133 | - |
|
134 | - $foundScope = false; |
|
135 | - $skipPtrs = []; |
|
136 | - foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) { |
|
137 | - if (isset($this->scopeTokens[$code]) === true) { |
|
138 | - $skipPtrs[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope); |
|
139 | - $foundScope = true; |
|
140 | - } |
|
141 | - } |
|
142 | - |
|
143 | - if ($this->listenOutside === true && $foundScope === false) { |
|
144 | - $skipPtrs[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr); |
|
145 | - } |
|
146 | - |
|
147 | - if (empty($skipPtrs) === false) { |
|
148 | - return min($skipPtrs); |
|
149 | - } |
|
150 | - |
|
151 | - return; |
|
152 | - |
|
153 | - }//end process() |
|
154 | - |
|
155 | - |
|
156 | - /** |
|
157 | - * Processes a token that is found within the scope that this test is |
|
158 | - * listening to. |
|
159 | - * |
|
160 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
161 | - * @param int $stackPtr The position in the stack where this |
|
162 | - * token was found. |
|
163 | - * @param int $currScope The position in the tokens array that |
|
164 | - * opened the scope that this test is |
|
165 | - * listening for. |
|
166 | - * |
|
167 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
168 | - * called again on the current file until the returned stack |
|
169 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
170 | - * the rest of the file. |
|
171 | - */ |
|
172 | - abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope); |
|
173 | - |
|
174 | - |
|
175 | - /** |
|
176 | - * Processes a token that is found outside the scope that this test is |
|
177 | - * listening to. |
|
178 | - * |
|
179 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
180 | - * @param int $stackPtr The position in the stack where this |
|
181 | - * token was found. |
|
182 | - * |
|
183 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
184 | - * called again on the current file until the returned stack |
|
185 | - * pointer is reached. Return (count($tokens) + 1) to skip |
|
186 | - * the rest of the file. |
|
187 | - */ |
|
188 | - abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr); |
|
35 | + /** |
|
36 | + * The token types that this test wishes to listen to within the scope. |
|
37 | + * |
|
38 | + * @var array |
|
39 | + */ |
|
40 | + private $tokens = []; |
|
41 | + |
|
42 | + /** |
|
43 | + * The type of scope opener tokens that this test wishes to listen to. |
|
44 | + * |
|
45 | + * @var string |
|
46 | + */ |
|
47 | + private $scopeTokens = []; |
|
48 | + |
|
49 | + /** |
|
50 | + * True if this test should fire on tokens outside of the scope. |
|
51 | + * |
|
52 | + * @var boolean |
|
53 | + */ |
|
54 | + private $listenOutside = false; |
|
55 | + |
|
56 | + |
|
57 | + /** |
|
58 | + * Constructs a new AbstractScopeTest. |
|
59 | + * |
|
60 | + * @param array $scopeTokens The type of scope the test wishes to listen to. |
|
61 | + * @param array $tokens The tokens that the test wishes to listen to |
|
62 | + * within the scope. |
|
63 | + * @param boolean $listenOutside If true this test will also alert the |
|
64 | + * extending class when a token is found outside |
|
65 | + * the scope, by calling the |
|
66 | + * processTokenOutsideScope method. |
|
67 | + * |
|
68 | + * @see PHP_CodeSniffer.getValidScopeTokeners() |
|
69 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens array is empty. |
|
70 | + */ |
|
71 | + public function __construct( |
|
72 | + array $scopeTokens, |
|
73 | + array $tokens, |
|
74 | + $listenOutside=false |
|
75 | + ) { |
|
76 | + if (empty($scopeTokens) === true) { |
|
77 | + $error = 'The scope tokens list cannot be empty'; |
|
78 | + throw new RuntimeException($error); |
|
79 | + } |
|
80 | + |
|
81 | + if (empty($tokens) === true) { |
|
82 | + $error = 'The tokens list cannot be empty'; |
|
83 | + throw new RuntimeException($error); |
|
84 | + } |
|
85 | + |
|
86 | + $invalidScopeTokens = array_intersect($scopeTokens, $tokens); |
|
87 | + if (empty($invalidScopeTokens) === false) { |
|
88 | + $invalid = implode(', ', $invalidScopeTokens); |
|
89 | + $error = "Scope tokens [$invalid] can't be in the tokens array"; |
|
90 | + throw new RuntimeException($error); |
|
91 | + } |
|
92 | + |
|
93 | + $this->listenOutside = $listenOutside; |
|
94 | + $this->scopeTokens = array_flip($scopeTokens); |
|
95 | + $this->tokens = $tokens; |
|
96 | + |
|
97 | + }//end __construct() |
|
98 | + |
|
99 | + |
|
100 | + /** |
|
101 | + * The method that is called to register the tokens this test wishes to |
|
102 | + * listen to. |
|
103 | + * |
|
104 | + * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register |
|
105 | + * for the desired tokens and scope. |
|
106 | + * |
|
107 | + * @return int[] |
|
108 | + * @see __constructor() |
|
109 | + */ |
|
110 | + final public function register() |
|
111 | + { |
|
112 | + return $this->tokens; |
|
113 | + |
|
114 | + }//end register() |
|
115 | + |
|
116 | + |
|
117 | + /** |
|
118 | + * Processes the tokens that this test is listening for. |
|
119 | + * |
|
120 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
121 | + * @param int $stackPtr The position in the stack where this |
|
122 | + * token was found. |
|
123 | + * |
|
124 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
125 | + * called again on the current file until the returned stack |
|
126 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
127 | + * the rest of the file. |
|
128 | + * @see processTokenWithinScope() |
|
129 | + */ |
|
130 | + final public function process(File $phpcsFile, $stackPtr) |
|
131 | + { |
|
132 | + $tokens = $phpcsFile->getTokens(); |
|
133 | + |
|
134 | + $foundScope = false; |
|
135 | + $skipPtrs = []; |
|
136 | + foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) { |
|
137 | + if (isset($this->scopeTokens[$code]) === true) { |
|
138 | + $skipPtrs[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope); |
|
139 | + $foundScope = true; |
|
140 | + } |
|
141 | + } |
|
142 | + |
|
143 | + if ($this->listenOutside === true && $foundScope === false) { |
|
144 | + $skipPtrs[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr); |
|
145 | + } |
|
146 | + |
|
147 | + if (empty($skipPtrs) === false) { |
|
148 | + return min($skipPtrs); |
|
149 | + } |
|
150 | + |
|
151 | + return; |
|
152 | + |
|
153 | + }//end process() |
|
154 | + |
|
155 | + |
|
156 | + /** |
|
157 | + * Processes a token that is found within the scope that this test is |
|
158 | + * listening to. |
|
159 | + * |
|
160 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
161 | + * @param int $stackPtr The position in the stack where this |
|
162 | + * token was found. |
|
163 | + * @param int $currScope The position in the tokens array that |
|
164 | + * opened the scope that this test is |
|
165 | + * listening for. |
|
166 | + * |
|
167 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
168 | + * called again on the current file until the returned stack |
|
169 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
170 | + * the rest of the file. |
|
171 | + */ |
|
172 | + abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope); |
|
173 | + |
|
174 | + |
|
175 | + /** |
|
176 | + * Processes a token that is found outside the scope that this test is |
|
177 | + * listening to. |
|
178 | + * |
|
179 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
180 | + * @param int $stackPtr The position in the stack where this |
|
181 | + * token was found. |
|
182 | + * |
|
183 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
184 | + * called again on the current file until the returned stack |
|
185 | + * pointer is reached. Return (count($tokens) + 1) to skip |
|
186 | + * the rest of the file. |
|
187 | + */ |
|
188 | + abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr); |
|
189 | 189 | |
190 | 190 | |
191 | 191 | }//end class |
@@ -17,920 +17,920 @@ |
||
17 | 17 | abstract class AbstractPatternSniff implements Sniff |
18 | 18 | { |
19 | 19 | |
20 | - /** |
|
21 | - * If true, comments will be ignored if they are found in the code. |
|
22 | - * |
|
23 | - * @var boolean |
|
24 | - */ |
|
25 | - public $ignoreComments = false; |
|
26 | - |
|
27 | - /** |
|
28 | - * The current file being checked. |
|
29 | - * |
|
30 | - * @var string |
|
31 | - */ |
|
32 | - protected $currFile = ''; |
|
33 | - |
|
34 | - /** |
|
35 | - * The parsed patterns array. |
|
36 | - * |
|
37 | - * @var array |
|
38 | - */ |
|
39 | - private $parsedPatterns = []; |
|
40 | - |
|
41 | - /** |
|
42 | - * Tokens that this sniff wishes to process outside of the patterns. |
|
43 | - * |
|
44 | - * @var int[] |
|
45 | - * @see registerSupplementary() |
|
46 | - * @see processSupplementary() |
|
47 | - */ |
|
48 | - private $supplementaryTokens = []; |
|
49 | - |
|
50 | - /** |
|
51 | - * Positions in the stack where errors have occurred. |
|
52 | - * |
|
53 | - * @var array<int, bool> |
|
54 | - */ |
|
55 | - private $errorPos = []; |
|
56 | - |
|
57 | - |
|
58 | - /** |
|
59 | - * Constructs a AbstractPatternSniff. |
|
60 | - * |
|
61 | - * @param boolean $ignoreComments If true, comments will be ignored. |
|
62 | - */ |
|
63 | - public function __construct($ignoreComments=null) |
|
64 | - { |
|
65 | - // This is here for backwards compatibility. |
|
66 | - if ($ignoreComments !== null) { |
|
67 | - $this->ignoreComments = $ignoreComments; |
|
68 | - } |
|
69 | - |
|
70 | - $this->supplementaryTokens = $this->registerSupplementary(); |
|
71 | - |
|
72 | - }//end __construct() |
|
73 | - |
|
74 | - |
|
75 | - /** |
|
76 | - * Registers the tokens to listen to. |
|
77 | - * |
|
78 | - * Classes extending <i>AbstractPatternTest</i> should implement the |
|
79 | - * <i>getPatterns()</i> method to register the patterns they wish to test. |
|
80 | - * |
|
81 | - * @return int[] |
|
82 | - * @see process() |
|
83 | - */ |
|
84 | - final public function register() |
|
85 | - { |
|
86 | - $listenTypes = []; |
|
87 | - $patterns = $this->getPatterns(); |
|
88 | - |
|
89 | - foreach ($patterns as $pattern) { |
|
90 | - $parsedPattern = $this->parse($pattern); |
|
91 | - |
|
92 | - // Find a token position in the pattern that we can use |
|
93 | - // for a listener token. |
|
94 | - $pos = $this->getListenerTokenPos($parsedPattern); |
|
95 | - $tokenType = $parsedPattern[$pos]['token']; |
|
96 | - $listenTypes[] = $tokenType; |
|
97 | - |
|
98 | - $patternArray = [ |
|
99 | - 'listen_pos' => $pos, |
|
100 | - 'pattern' => $parsedPattern, |
|
101 | - 'pattern_code' => $pattern, |
|
102 | - ]; |
|
103 | - |
|
104 | - if (isset($this->parsedPatterns[$tokenType]) === false) { |
|
105 | - $this->parsedPatterns[$tokenType] = []; |
|
106 | - } |
|
107 | - |
|
108 | - $this->parsedPatterns[$tokenType][] = $patternArray; |
|
109 | - }//end foreach |
|
110 | - |
|
111 | - return array_unique(array_merge($listenTypes, $this->supplementaryTokens)); |
|
112 | - |
|
113 | - }//end register() |
|
114 | - |
|
115 | - |
|
116 | - /** |
|
117 | - * Returns the token types that the specified pattern is checking for. |
|
118 | - * |
|
119 | - * Returned array is in the format: |
|
120 | - * <code> |
|
121 | - * array( |
|
122 | - * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token |
|
123 | - * // should occur in the pattern. |
|
124 | - * ); |
|
125 | - * </code> |
|
126 | - * |
|
127 | - * @param array $pattern The parsed pattern to find the acquire the token |
|
128 | - * types from. |
|
129 | - * |
|
130 | - * @return array<int, int> |
|
131 | - */ |
|
132 | - private function getPatternTokenTypes($pattern) |
|
133 | - { |
|
134 | - $tokenTypes = []; |
|
135 | - foreach ($pattern as $pos => $patternInfo) { |
|
136 | - if ($patternInfo['type'] === 'token') { |
|
137 | - if (isset($tokenTypes[$patternInfo['token']]) === false) { |
|
138 | - $tokenTypes[$patternInfo['token']] = $pos; |
|
139 | - } |
|
140 | - } |
|
141 | - } |
|
142 | - |
|
143 | - return $tokenTypes; |
|
144 | - |
|
145 | - }//end getPatternTokenTypes() |
|
146 | - |
|
147 | - |
|
148 | - /** |
|
149 | - * Returns the position in the pattern that this test should register as |
|
150 | - * a listener for the pattern. |
|
151 | - * |
|
152 | - * @param array $pattern The pattern to acquire the listener for. |
|
153 | - * |
|
154 | - * @return int The position in the pattern that this test should register |
|
155 | - * as the listener. |
|
156 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for. |
|
157 | - */ |
|
158 | - private function getListenerTokenPos($pattern) |
|
159 | - { |
|
160 | - $tokenTypes = $this->getPatternTokenTypes($pattern); |
|
161 | - $tokenCodes = array_keys($tokenTypes); |
|
162 | - $token = Tokens::getHighestWeightedToken($tokenCodes); |
|
163 | - |
|
164 | - // If we could not get a token. |
|
165 | - if ($token === false) { |
|
166 | - $error = 'Could not determine a token to listen for'; |
|
167 | - throw new RuntimeException($error); |
|
168 | - } |
|
169 | - |
|
170 | - return $tokenTypes[$token]; |
|
171 | - |
|
172 | - }//end getListenerTokenPos() |
|
173 | - |
|
174 | - |
|
175 | - /** |
|
176 | - * Processes the test. |
|
177 | - * |
|
178 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
179 | - * token occurred. |
|
180 | - * @param int $stackPtr The position in the tokens stack |
|
181 | - * where the listening token type |
|
182 | - * was found. |
|
183 | - * |
|
184 | - * @return void |
|
185 | - * @see register() |
|
186 | - */ |
|
187 | - final public function process(File $phpcsFile, $stackPtr) |
|
188 | - { |
|
189 | - $file = $phpcsFile->getFilename(); |
|
190 | - if ($this->currFile !== $file) { |
|
191 | - // We have changed files, so clean up. |
|
192 | - $this->errorPos = []; |
|
193 | - $this->currFile = $file; |
|
194 | - } |
|
195 | - |
|
196 | - $tokens = $phpcsFile->getTokens(); |
|
197 | - |
|
198 | - if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) { |
|
199 | - $this->processSupplementary($phpcsFile, $stackPtr); |
|
200 | - } |
|
201 | - |
|
202 | - $type = $tokens[$stackPtr]['code']; |
|
203 | - |
|
204 | - // If the type is not set, then it must have been a token registered |
|
205 | - // with registerSupplementary(). |
|
206 | - if (isset($this->parsedPatterns[$type]) === false) { |
|
207 | - return; |
|
208 | - } |
|
209 | - |
|
210 | - $allErrors = []; |
|
211 | - |
|
212 | - // Loop over each pattern that is listening to the current token type |
|
213 | - // that we are processing. |
|
214 | - foreach ($this->parsedPatterns[$type] as $patternInfo) { |
|
215 | - // If processPattern returns false, then the pattern that we are |
|
216 | - // checking the code with must not be designed to check that code. |
|
217 | - $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); |
|
218 | - if ($errors === false) { |
|
219 | - // The pattern didn't match. |
|
220 | - continue; |
|
221 | - } else if (empty($errors) === true) { |
|
222 | - // The pattern matched, but there were no errors. |
|
223 | - break; |
|
224 | - } |
|
225 | - |
|
226 | - foreach ($errors as $stackPtr => $error) { |
|
227 | - if (isset($this->errorPos[$stackPtr]) === false) { |
|
228 | - $this->errorPos[$stackPtr] = true; |
|
229 | - $allErrors[$stackPtr] = $error; |
|
230 | - } |
|
231 | - } |
|
232 | - } |
|
233 | - |
|
234 | - foreach ($allErrors as $stackPtr => $error) { |
|
235 | - $phpcsFile->addError($error, $stackPtr, 'Found'); |
|
236 | - } |
|
237 | - |
|
238 | - }//end process() |
|
239 | - |
|
240 | - |
|
241 | - /** |
|
242 | - * Processes the pattern and verifies the code at $stackPtr. |
|
243 | - * |
|
244 | - * @param array $patternInfo Information about the pattern used |
|
245 | - * for checking, which includes are |
|
246 | - * parsed token representation of the |
|
247 | - * pattern. |
|
248 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
249 | - * token occurred. |
|
250 | - * @param int $stackPtr The position in the tokens stack where |
|
251 | - * the listening token type was found. |
|
252 | - * |
|
253 | - * @return array |
|
254 | - */ |
|
255 | - protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) |
|
256 | - { |
|
257 | - $tokens = $phpcsFile->getTokens(); |
|
258 | - $pattern = $patternInfo['pattern']; |
|
259 | - $patternCode = $patternInfo['pattern_code']; |
|
260 | - $errors = []; |
|
261 | - $found = ''; |
|
262 | - |
|
263 | - $ignoreTokens = [T_WHITESPACE => T_WHITESPACE]; |
|
264 | - if ($this->ignoreComments === true) { |
|
265 | - $ignoreTokens += Tokens::$commentTokens; |
|
266 | - } |
|
267 | - |
|
268 | - $origStackPtr = $stackPtr; |
|
269 | - $hasError = false; |
|
270 | - |
|
271 | - if ($patternInfo['listen_pos'] > 0) { |
|
272 | - $stackPtr--; |
|
273 | - |
|
274 | - for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { |
|
275 | - if ($pattern[$i]['type'] === 'token') { |
|
276 | - if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
277 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
278 | - $found = $tokens[$stackPtr]['content'].$found; |
|
279 | - } |
|
280 | - |
|
281 | - // Only check the size of the whitespace if this is not |
|
282 | - // the first token. We don't care about the size of |
|
283 | - // leading whitespace, just that there is some. |
|
284 | - if ($i !== 0) { |
|
285 | - if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { |
|
286 | - $hasError = true; |
|
287 | - } |
|
288 | - } |
|
289 | - } else { |
|
290 | - // Check to see if this important token is the same as the |
|
291 | - // previous important token in the pattern. If it is not, |
|
292 | - // then the pattern cannot be for this piece of code. |
|
293 | - $prev = $phpcsFile->findPrevious( |
|
294 | - $ignoreTokens, |
|
295 | - $stackPtr, |
|
296 | - null, |
|
297 | - true |
|
298 | - ); |
|
299 | - |
|
300 | - if ($prev === false |
|
301 | - || $tokens[$prev]['code'] !== $pattern[$i]['token'] |
|
302 | - ) { |
|
303 | - return false; |
|
304 | - } |
|
305 | - |
|
306 | - // If we skipped past some whitespace tokens, then add them |
|
307 | - // to the found string. |
|
308 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
309 | - ($prev + 1), |
|
310 | - ($stackPtr - $prev - 1) |
|
311 | - ); |
|
312 | - |
|
313 | - $found = $tokens[$prev]['content'].$tokenContent.$found; |
|
314 | - |
|
315 | - if (isset($pattern[($i - 1)]) === true |
|
316 | - && $pattern[($i - 1)]['type'] === 'skip' |
|
317 | - ) { |
|
318 | - $stackPtr = $prev; |
|
319 | - } else { |
|
320 | - $stackPtr = ($prev - 1); |
|
321 | - } |
|
322 | - }//end if |
|
323 | - } else if ($pattern[$i]['type'] === 'skip') { |
|
324 | - // Skip to next piece of relevant code. |
|
325 | - if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
326 | - $to = 'parenthesis_opener'; |
|
327 | - } else { |
|
328 | - $to = 'scope_opener'; |
|
329 | - } |
|
330 | - |
|
331 | - // Find the previous opener. |
|
332 | - $next = $phpcsFile->findPrevious( |
|
333 | - $ignoreTokens, |
|
334 | - $stackPtr, |
|
335 | - null, |
|
336 | - true |
|
337 | - ); |
|
338 | - |
|
339 | - if ($next === false || isset($tokens[$next][$to]) === false) { |
|
340 | - // If there was not opener, then we must be |
|
341 | - // using the wrong pattern. |
|
342 | - return false; |
|
343 | - } |
|
344 | - |
|
345 | - if ($to === 'parenthesis_opener') { |
|
346 | - $found = '{'.$found; |
|
347 | - } else { |
|
348 | - $found = '('.$found; |
|
349 | - } |
|
350 | - |
|
351 | - $found = '...'.$found; |
|
352 | - |
|
353 | - // Skip to the opening token. |
|
354 | - $stackPtr = ($tokens[$next][$to] - 1); |
|
355 | - } else if ($pattern[$i]['type'] === 'string') { |
|
356 | - $found = 'abc'; |
|
357 | - } else if ($pattern[$i]['type'] === 'newline') { |
|
358 | - if ($this->ignoreComments === true |
|
359 | - && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true |
|
360 | - ) { |
|
361 | - $startComment = $phpcsFile->findPrevious( |
|
362 | - Tokens::$commentTokens, |
|
363 | - ($stackPtr - 1), |
|
364 | - null, |
|
365 | - true |
|
366 | - ); |
|
367 | - |
|
368 | - if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) { |
|
369 | - $startComment++; |
|
370 | - } |
|
371 | - |
|
372 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
373 | - $startComment, |
|
374 | - ($stackPtr - $startComment + 1) |
|
375 | - ); |
|
376 | - |
|
377 | - $found = $tokenContent.$found; |
|
378 | - $stackPtr = ($startComment - 1); |
|
379 | - } |
|
380 | - |
|
381 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
382 | - if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { |
|
383 | - $found = $tokens[$stackPtr]['content'].$found; |
|
384 | - |
|
385 | - // This may just be an indent that comes after a newline |
|
386 | - // so check the token before to make sure. If it is a newline, we |
|
387 | - // can ignore the error here. |
|
388 | - if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar) |
|
389 | - && ($this->ignoreComments === true |
|
390 | - && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false) |
|
391 | - ) { |
|
392 | - $hasError = true; |
|
393 | - } else { |
|
394 | - $stackPtr--; |
|
395 | - } |
|
396 | - } else { |
|
397 | - $found = 'EOL'.$found; |
|
398 | - } |
|
399 | - } else { |
|
400 | - $found = $tokens[$stackPtr]['content'].$found; |
|
401 | - $hasError = true; |
|
402 | - }//end if |
|
403 | - |
|
404 | - if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') { |
|
405 | - // Make sure they only have 1 newline. |
|
406 | - $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true); |
|
407 | - if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { |
|
408 | - $hasError = true; |
|
409 | - } |
|
410 | - } |
|
411 | - }//end if |
|
412 | - }//end for |
|
413 | - }//end if |
|
414 | - |
|
415 | - $stackPtr = $origStackPtr; |
|
416 | - $lastAddedStackPtr = null; |
|
417 | - $patternLen = count($pattern); |
|
418 | - |
|
419 | - for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { |
|
420 | - if (isset($tokens[$stackPtr]) === false) { |
|
421 | - break; |
|
422 | - } |
|
423 | - |
|
424 | - if ($pattern[$i]['type'] === 'token') { |
|
425 | - if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
426 | - if ($this->ignoreComments === true) { |
|
427 | - // If we are ignoring comments, check to see if this current |
|
428 | - // token is a comment. If so skip it. |
|
429 | - if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { |
|
430 | - continue; |
|
431 | - } |
|
432 | - |
|
433 | - // If the next token is a comment, the we need to skip the |
|
434 | - // current token as we should allow a space before a |
|
435 | - // comment for readability. |
|
436 | - if (isset($tokens[($stackPtr + 1)]) === true |
|
437 | - && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true |
|
438 | - ) { |
|
439 | - continue; |
|
440 | - } |
|
441 | - } |
|
442 | - |
|
443 | - $tokenContent = ''; |
|
444 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
445 | - if (isset($pattern[($i + 1)]) === false) { |
|
446 | - // This is the last token in the pattern, so just compare |
|
447 | - // the next token of content. |
|
448 | - $tokenContent = $tokens[$stackPtr]['content']; |
|
449 | - } else { |
|
450 | - // Get all the whitespace to the next token. |
|
451 | - $next = $phpcsFile->findNext( |
|
452 | - Tokens::$emptyTokens, |
|
453 | - $stackPtr, |
|
454 | - null, |
|
455 | - true |
|
456 | - ); |
|
457 | - |
|
458 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
459 | - $stackPtr, |
|
460 | - ($next - $stackPtr) |
|
461 | - ); |
|
462 | - |
|
463 | - $lastAddedStackPtr = $stackPtr; |
|
464 | - $stackPtr = $next; |
|
465 | - }//end if |
|
466 | - |
|
467 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
468 | - $found .= $tokenContent; |
|
469 | - } |
|
470 | - } else { |
|
471 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
472 | - $found .= $tokens[$stackPtr]['content']; |
|
473 | - $lastAddedStackPtr = $stackPtr; |
|
474 | - } |
|
475 | - }//end if |
|
476 | - |
|
477 | - if (isset($pattern[($i + 1)]) === true |
|
478 | - && $pattern[($i + 1)]['type'] === 'skip' |
|
479 | - ) { |
|
480 | - // The next token is a skip token, so we just need to make |
|
481 | - // sure the whitespace we found has *at least* the |
|
482 | - // whitespace required. |
|
483 | - if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { |
|
484 | - $hasError = true; |
|
485 | - } |
|
486 | - } else { |
|
487 | - if ($tokenContent !== $pattern[$i]['value']) { |
|
488 | - $hasError = true; |
|
489 | - } |
|
490 | - } |
|
491 | - } else { |
|
492 | - // Check to see if this important token is the same as the |
|
493 | - // next important token in the pattern. If it is not, then |
|
494 | - // the pattern cannot be for this piece of code. |
|
495 | - $next = $phpcsFile->findNext( |
|
496 | - $ignoreTokens, |
|
497 | - $stackPtr, |
|
498 | - null, |
|
499 | - true |
|
500 | - ); |
|
501 | - |
|
502 | - if ($next === false |
|
503 | - || $tokens[$next]['code'] !== $pattern[$i]['token'] |
|
504 | - ) { |
|
505 | - // The next important token did not match the pattern. |
|
506 | - return false; |
|
507 | - } |
|
508 | - |
|
509 | - if ($lastAddedStackPtr !== null) { |
|
510 | - if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET |
|
511 | - || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) |
|
512 | - && isset($tokens[$next]['scope_condition']) === true |
|
513 | - && $tokens[$next]['scope_condition'] > $lastAddedStackPtr |
|
514 | - ) { |
|
515 | - // This is a brace, but the owner of it is after the current |
|
516 | - // token, which means it does not belong to any token in |
|
517 | - // our pattern. This means the pattern is not for us. |
|
518 | - return false; |
|
519 | - } |
|
520 | - |
|
521 | - if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS |
|
522 | - || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) |
|
523 | - && isset($tokens[$next]['parenthesis_owner']) === true |
|
524 | - && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr |
|
525 | - ) { |
|
526 | - // This is a bracket, but the owner of it is after the current |
|
527 | - // token, which means it does not belong to any token in |
|
528 | - // our pattern. This means the pattern is not for us. |
|
529 | - return false; |
|
530 | - } |
|
531 | - }//end if |
|
532 | - |
|
533 | - // If we skipped past some whitespace tokens, then add them |
|
534 | - // to the found string. |
|
535 | - if (($next - $stackPtr) > 0) { |
|
536 | - $hasComment = false; |
|
537 | - for ($j = $stackPtr; $j < $next; $j++) { |
|
538 | - $found .= $tokens[$j]['content']; |
|
539 | - if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) { |
|
540 | - $hasComment = true; |
|
541 | - } |
|
542 | - } |
|
543 | - |
|
544 | - // If we are not ignoring comments, this additional |
|
545 | - // whitespace or comment is not allowed. If we are |
|
546 | - // ignoring comments, there needs to be at least one |
|
547 | - // comment for this to be allowed. |
|
548 | - if ($this->ignoreComments === false |
|
549 | - || ($this->ignoreComments === true |
|
550 | - && $hasComment === false) |
|
551 | - ) { |
|
552 | - $hasError = true; |
|
553 | - } |
|
554 | - |
|
555 | - // Even when ignoring comments, we are not allowed to include |
|
556 | - // newlines without the pattern specifying them, so |
|
557 | - // everything should be on the same line. |
|
558 | - if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { |
|
559 | - $hasError = true; |
|
560 | - } |
|
561 | - }//end if |
|
562 | - |
|
563 | - if ($next !== $lastAddedStackPtr) { |
|
564 | - $found .= $tokens[$next]['content']; |
|
565 | - $lastAddedStackPtr = $next; |
|
566 | - } |
|
567 | - |
|
568 | - if (isset($pattern[($i + 1)]) === true |
|
569 | - && $pattern[($i + 1)]['type'] === 'skip' |
|
570 | - ) { |
|
571 | - $stackPtr = $next; |
|
572 | - } else { |
|
573 | - $stackPtr = ($next + 1); |
|
574 | - } |
|
575 | - }//end if |
|
576 | - } else if ($pattern[$i]['type'] === 'skip') { |
|
577 | - if ($pattern[$i]['to'] === 'unknown') { |
|
578 | - $next = $phpcsFile->findNext( |
|
579 | - $pattern[($i + 1)]['token'], |
|
580 | - $stackPtr |
|
581 | - ); |
|
582 | - |
|
583 | - if ($next === false) { |
|
584 | - // Couldn't find the next token, so we must |
|
585 | - // be using the wrong pattern. |
|
586 | - return false; |
|
587 | - } |
|
588 | - |
|
589 | - $found .= '...'; |
|
590 | - $stackPtr = $next; |
|
591 | - } else { |
|
592 | - // Find the previous opener. |
|
593 | - $next = $phpcsFile->findPrevious( |
|
594 | - Tokens::$blockOpeners, |
|
595 | - $stackPtr |
|
596 | - ); |
|
597 | - |
|
598 | - if ($next === false |
|
599 | - || isset($tokens[$next][$pattern[$i]['to']]) === false |
|
600 | - ) { |
|
601 | - // If there was not opener, then we must |
|
602 | - // be using the wrong pattern. |
|
603 | - return false; |
|
604 | - } |
|
605 | - |
|
606 | - $found .= '...'; |
|
607 | - if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
608 | - $found .= ')'; |
|
609 | - } else { |
|
610 | - $found .= '}'; |
|
611 | - } |
|
612 | - |
|
613 | - // Skip to the closing token. |
|
614 | - $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); |
|
615 | - }//end if |
|
616 | - } else if ($pattern[$i]['type'] === 'string') { |
|
617 | - if ($tokens[$stackPtr]['code'] !== T_STRING) { |
|
618 | - $hasError = true; |
|
619 | - } |
|
620 | - |
|
621 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
622 | - $found .= 'abc'; |
|
623 | - $lastAddedStackPtr = $stackPtr; |
|
624 | - } |
|
625 | - |
|
626 | - $stackPtr++; |
|
627 | - } else if ($pattern[$i]['type'] === 'newline') { |
|
628 | - // Find the next token that contains a newline character. |
|
629 | - $newline = 0; |
|
630 | - for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { |
|
631 | - if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { |
|
632 | - $newline = $j; |
|
633 | - break; |
|
634 | - } |
|
635 | - } |
|
636 | - |
|
637 | - if ($newline === 0) { |
|
638 | - // We didn't find a newline character in the rest of the file. |
|
639 | - $next = ($phpcsFile->numTokens - 1); |
|
640 | - $hasError = true; |
|
641 | - } else { |
|
642 | - if ($this->ignoreComments === false) { |
|
643 | - // The newline character cannot be part of a comment. |
|
644 | - if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { |
|
645 | - $hasError = true; |
|
646 | - } |
|
647 | - } |
|
648 | - |
|
649 | - if ($newline === $stackPtr) { |
|
650 | - $next = ($stackPtr + 1); |
|
651 | - } else { |
|
652 | - // Check that there were no significant tokens that we |
|
653 | - // skipped over to find our newline character. |
|
654 | - $next = $phpcsFile->findNext( |
|
655 | - $ignoreTokens, |
|
656 | - $stackPtr, |
|
657 | - null, |
|
658 | - true |
|
659 | - ); |
|
660 | - |
|
661 | - if ($next < $newline) { |
|
662 | - // We skipped a non-ignored token. |
|
663 | - $hasError = true; |
|
664 | - } else { |
|
665 | - $next = ($newline + 1); |
|
666 | - } |
|
667 | - } |
|
668 | - }//end if |
|
669 | - |
|
670 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
671 | - $found .= $phpcsFile->getTokensAsString( |
|
672 | - $stackPtr, |
|
673 | - ($next - $stackPtr) |
|
674 | - ); |
|
675 | - |
|
676 | - $lastAddedStackPtr = ($next - 1); |
|
677 | - } |
|
678 | - |
|
679 | - $stackPtr = $next; |
|
680 | - }//end if |
|
681 | - }//end for |
|
682 | - |
|
683 | - if ($hasError === true) { |
|
684 | - $error = $this->prepareError($found, $patternCode); |
|
685 | - $errors[$origStackPtr] = $error; |
|
686 | - } |
|
687 | - |
|
688 | - return $errors; |
|
689 | - |
|
690 | - }//end processPattern() |
|
691 | - |
|
692 | - |
|
693 | - /** |
|
694 | - * Prepares an error for the specified patternCode. |
|
695 | - * |
|
696 | - * @param string $found The actual found string in the code. |
|
697 | - * @param string $patternCode The expected pattern code. |
|
698 | - * |
|
699 | - * @return string The error message. |
|
700 | - */ |
|
701 | - protected function prepareError($found, $patternCode) |
|
702 | - { |
|
703 | - $found = str_replace("\r\n", '\n', $found); |
|
704 | - $found = str_replace("\n", '\n', $found); |
|
705 | - $found = str_replace("\r", '\n', $found); |
|
706 | - $found = str_replace("\t", '\t', $found); |
|
707 | - $found = str_replace('EOL', '\n', $found); |
|
708 | - $expected = str_replace('EOL', '\n', $patternCode); |
|
709 | - |
|
710 | - $error = "Expected \"$expected\"; found \"$found\""; |
|
711 | - |
|
712 | - return $error; |
|
713 | - |
|
714 | - }//end prepareError() |
|
715 | - |
|
716 | - |
|
717 | - /** |
|
718 | - * Returns the patterns that should be checked. |
|
719 | - * |
|
720 | - * @return string[] |
|
721 | - */ |
|
722 | - abstract protected function getPatterns(); |
|
723 | - |
|
724 | - |
|
725 | - /** |
|
726 | - * Registers any supplementary tokens that this test might wish to process. |
|
727 | - * |
|
728 | - * A sniff may wish to register supplementary tests when it wishes to group |
|
729 | - * an arbitrary validation that cannot be performed using a pattern, with |
|
730 | - * other pattern tests. |
|
731 | - * |
|
732 | - * @return int[] |
|
733 | - * @see processSupplementary() |
|
734 | - */ |
|
735 | - protected function registerSupplementary() |
|
736 | - { |
|
737 | - return []; |
|
738 | - |
|
739 | - }//end registerSupplementary() |
|
740 | - |
|
741 | - |
|
742 | - /** |
|
743 | - * Processes any tokens registered with registerSupplementary(). |
|
744 | - * |
|
745 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to |
|
746 | - * process the skip. |
|
747 | - * @param int $stackPtr The position in the tokens stack to |
|
748 | - * process. |
|
749 | - * |
|
750 | - * @return void |
|
751 | - * @see registerSupplementary() |
|
752 | - */ |
|
753 | - protected function processSupplementary(File $phpcsFile, $stackPtr) |
|
754 | - { |
|
755 | - |
|
756 | - }//end processSupplementary() |
|
757 | - |
|
758 | - |
|
759 | - /** |
|
760 | - * Parses a pattern string into an array of pattern steps. |
|
761 | - * |
|
762 | - * @param string $pattern The pattern to parse. |
|
763 | - * |
|
764 | - * @return array The parsed pattern array. |
|
765 | - * @see createSkipPattern() |
|
766 | - * @see createTokenPattern() |
|
767 | - */ |
|
768 | - private function parse($pattern) |
|
769 | - { |
|
770 | - $patterns = []; |
|
771 | - $length = strlen($pattern); |
|
772 | - $lastToken = 0; |
|
773 | - $firstToken = 0; |
|
774 | - |
|
775 | - for ($i = 0; $i < $length; $i++) { |
|
776 | - $specialPattern = false; |
|
777 | - $isLastChar = ($i === ($length - 1)); |
|
778 | - $oldFirstToken = $firstToken; |
|
779 | - |
|
780 | - if (substr($pattern, $i, 3) === '...') { |
|
781 | - // It's a skip pattern. The skip pattern requires the |
|
782 | - // content of the token in the "from" position and the token |
|
783 | - // to skip to. |
|
784 | - $specialPattern = $this->createSkipPattern($pattern, ($i - 1)); |
|
785 | - $lastToken = ($i - $firstToken); |
|
786 | - $firstToken = ($i + 3); |
|
787 | - $i += 2; |
|
788 | - |
|
789 | - if ($specialPattern['to'] !== 'unknown') { |
|
790 | - $firstToken++; |
|
791 | - } |
|
792 | - } else if (substr($pattern, $i, 3) === 'abc') { |
|
793 | - $specialPattern = ['type' => 'string']; |
|
794 | - $lastToken = ($i - $firstToken); |
|
795 | - $firstToken = ($i + 3); |
|
796 | - $i += 2; |
|
797 | - } else if (substr($pattern, $i, 3) === 'EOL') { |
|
798 | - $specialPattern = ['type' => 'newline']; |
|
799 | - $lastToken = ($i - $firstToken); |
|
800 | - $firstToken = ($i + 3); |
|
801 | - $i += 2; |
|
802 | - }//end if |
|
803 | - |
|
804 | - if ($specialPattern !== false || $isLastChar === true) { |
|
805 | - // If we are at the end of the string, don't worry about a limit. |
|
806 | - if ($isLastChar === true) { |
|
807 | - // Get the string from the end of the last skip pattern, if any, |
|
808 | - // to the end of the pattern string. |
|
809 | - $str = substr($pattern, $oldFirstToken); |
|
810 | - } else { |
|
811 | - // Get the string from the end of the last special pattern, |
|
812 | - // if any, to the start of this special pattern. |
|
813 | - if ($lastToken === 0) { |
|
814 | - // Note that if the last special token was zero characters ago, |
|
815 | - // there will be nothing to process so we can skip this bit. |
|
816 | - // This happens if you have something like: EOL... in your pattern. |
|
817 | - $str = ''; |
|
818 | - } else { |
|
819 | - $str = substr($pattern, $oldFirstToken, $lastToken); |
|
820 | - } |
|
821 | - } |
|
822 | - |
|
823 | - if ($str !== '') { |
|
824 | - $tokenPatterns = $this->createTokenPattern($str); |
|
825 | - foreach ($tokenPatterns as $tokenPattern) { |
|
826 | - $patterns[] = $tokenPattern; |
|
827 | - } |
|
828 | - } |
|
829 | - |
|
830 | - // Make sure we don't skip the last token. |
|
831 | - if ($isLastChar === false && $i === ($length - 1)) { |
|
832 | - $i--; |
|
833 | - } |
|
834 | - }//end if |
|
835 | - |
|
836 | - // Add the skip pattern *after* we have processed |
|
837 | - // all the tokens from the end of the last skip pattern |
|
838 | - // to the start of this skip pattern. |
|
839 | - if ($specialPattern !== false) { |
|
840 | - $patterns[] = $specialPattern; |
|
841 | - } |
|
842 | - }//end for |
|
843 | - |
|
844 | - return $patterns; |
|
845 | - |
|
846 | - }//end parse() |
|
847 | - |
|
848 | - |
|
849 | - /** |
|
850 | - * Creates a skip pattern. |
|
851 | - * |
|
852 | - * @param string $pattern The pattern being parsed. |
|
853 | - * @param string $from The token content that the skip pattern starts from. |
|
854 | - * |
|
855 | - * @return array The pattern step. |
|
856 | - * @see createTokenPattern() |
|
857 | - * @see parse() |
|
858 | - */ |
|
859 | - private function createSkipPattern($pattern, $from) |
|
860 | - { |
|
861 | - $skip = ['type' => 'skip']; |
|
862 | - |
|
863 | - $nestedParenthesis = 0; |
|
864 | - $nestedBraces = 0; |
|
865 | - for ($start = $from; $start >= 0; $start--) { |
|
866 | - switch ($pattern[$start]) { |
|
867 | - case '(': |
|
868 | - if ($nestedParenthesis === 0) { |
|
869 | - $skip['to'] = 'parenthesis_closer'; |
|
870 | - } |
|
871 | - |
|
872 | - $nestedParenthesis--; |
|
873 | - break; |
|
874 | - case '{': |
|
875 | - if ($nestedBraces === 0) { |
|
876 | - $skip['to'] = 'scope_closer'; |
|
877 | - } |
|
878 | - |
|
879 | - $nestedBraces--; |
|
880 | - break; |
|
881 | - case '}': |
|
882 | - $nestedBraces++; |
|
883 | - break; |
|
884 | - case ')': |
|
885 | - $nestedParenthesis++; |
|
886 | - break; |
|
887 | - }//end switch |
|
888 | - |
|
889 | - if (isset($skip['to']) === true) { |
|
890 | - break; |
|
891 | - } |
|
892 | - }//end for |
|
893 | - |
|
894 | - if (isset($skip['to']) === false) { |
|
895 | - $skip['to'] = 'unknown'; |
|
896 | - } |
|
897 | - |
|
898 | - return $skip; |
|
899 | - |
|
900 | - }//end createSkipPattern() |
|
901 | - |
|
902 | - |
|
903 | - /** |
|
904 | - * Creates a token pattern. |
|
905 | - * |
|
906 | - * @param string $str The tokens string that the pattern should match. |
|
907 | - * |
|
908 | - * @return array The pattern step. |
|
909 | - * @see createSkipPattern() |
|
910 | - * @see parse() |
|
911 | - */ |
|
912 | - private function createTokenPattern($str) |
|
913 | - { |
|
914 | - // Don't add a space after the closing php tag as it will add a new |
|
915 | - // whitespace token. |
|
916 | - $tokenizer = new PHP('<?php '.$str.'?>', null); |
|
917 | - |
|
918 | - // Remove the <?php tag from the front and the end php tag from the back. |
|
919 | - $tokens = $tokenizer->getTokens(); |
|
920 | - $tokens = array_slice($tokens, 1, (count($tokens) - 2)); |
|
921 | - |
|
922 | - $patterns = []; |
|
923 | - foreach ($tokens as $patternInfo) { |
|
924 | - $patterns[] = [ |
|
925 | - 'type' => 'token', |
|
926 | - 'token' => $patternInfo['code'], |
|
927 | - 'value' => $patternInfo['content'], |
|
928 | - ]; |
|
929 | - } |
|
930 | - |
|
931 | - return $patterns; |
|
932 | - |
|
933 | - }//end createTokenPattern() |
|
20 | + /** |
|
21 | + * If true, comments will be ignored if they are found in the code. |
|
22 | + * |
|
23 | + * @var boolean |
|
24 | + */ |
|
25 | + public $ignoreComments = false; |
|
26 | + |
|
27 | + /** |
|
28 | + * The current file being checked. |
|
29 | + * |
|
30 | + * @var string |
|
31 | + */ |
|
32 | + protected $currFile = ''; |
|
33 | + |
|
34 | + /** |
|
35 | + * The parsed patterns array. |
|
36 | + * |
|
37 | + * @var array |
|
38 | + */ |
|
39 | + private $parsedPatterns = []; |
|
40 | + |
|
41 | + /** |
|
42 | + * Tokens that this sniff wishes to process outside of the patterns. |
|
43 | + * |
|
44 | + * @var int[] |
|
45 | + * @see registerSupplementary() |
|
46 | + * @see processSupplementary() |
|
47 | + */ |
|
48 | + private $supplementaryTokens = []; |
|
49 | + |
|
50 | + /** |
|
51 | + * Positions in the stack where errors have occurred. |
|
52 | + * |
|
53 | + * @var array<int, bool> |
|
54 | + */ |
|
55 | + private $errorPos = []; |
|
56 | + |
|
57 | + |
|
58 | + /** |
|
59 | + * Constructs a AbstractPatternSniff. |
|
60 | + * |
|
61 | + * @param boolean $ignoreComments If true, comments will be ignored. |
|
62 | + */ |
|
63 | + public function __construct($ignoreComments=null) |
|
64 | + { |
|
65 | + // This is here for backwards compatibility. |
|
66 | + if ($ignoreComments !== null) { |
|
67 | + $this->ignoreComments = $ignoreComments; |
|
68 | + } |
|
69 | + |
|
70 | + $this->supplementaryTokens = $this->registerSupplementary(); |
|
71 | + |
|
72 | + }//end __construct() |
|
73 | + |
|
74 | + |
|
75 | + /** |
|
76 | + * Registers the tokens to listen to. |
|
77 | + * |
|
78 | + * Classes extending <i>AbstractPatternTest</i> should implement the |
|
79 | + * <i>getPatterns()</i> method to register the patterns they wish to test. |
|
80 | + * |
|
81 | + * @return int[] |
|
82 | + * @see process() |
|
83 | + */ |
|
84 | + final public function register() |
|
85 | + { |
|
86 | + $listenTypes = []; |
|
87 | + $patterns = $this->getPatterns(); |
|
88 | + |
|
89 | + foreach ($patterns as $pattern) { |
|
90 | + $parsedPattern = $this->parse($pattern); |
|
91 | + |
|
92 | + // Find a token position in the pattern that we can use |
|
93 | + // for a listener token. |
|
94 | + $pos = $this->getListenerTokenPos($parsedPattern); |
|
95 | + $tokenType = $parsedPattern[$pos]['token']; |
|
96 | + $listenTypes[] = $tokenType; |
|
97 | + |
|
98 | + $patternArray = [ |
|
99 | + 'listen_pos' => $pos, |
|
100 | + 'pattern' => $parsedPattern, |
|
101 | + 'pattern_code' => $pattern, |
|
102 | + ]; |
|
103 | + |
|
104 | + if (isset($this->parsedPatterns[$tokenType]) === false) { |
|
105 | + $this->parsedPatterns[$tokenType] = []; |
|
106 | + } |
|
107 | + |
|
108 | + $this->parsedPatterns[$tokenType][] = $patternArray; |
|
109 | + }//end foreach |
|
110 | + |
|
111 | + return array_unique(array_merge($listenTypes, $this->supplementaryTokens)); |
|
112 | + |
|
113 | + }//end register() |
|
114 | + |
|
115 | + |
|
116 | + /** |
|
117 | + * Returns the token types that the specified pattern is checking for. |
|
118 | + * |
|
119 | + * Returned array is in the format: |
|
120 | + * <code> |
|
121 | + * array( |
|
122 | + * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token |
|
123 | + * // should occur in the pattern. |
|
124 | + * ); |
|
125 | + * </code> |
|
126 | + * |
|
127 | + * @param array $pattern The parsed pattern to find the acquire the token |
|
128 | + * types from. |
|
129 | + * |
|
130 | + * @return array<int, int> |
|
131 | + */ |
|
132 | + private function getPatternTokenTypes($pattern) |
|
133 | + { |
|
134 | + $tokenTypes = []; |
|
135 | + foreach ($pattern as $pos => $patternInfo) { |
|
136 | + if ($patternInfo['type'] === 'token') { |
|
137 | + if (isset($tokenTypes[$patternInfo['token']]) === false) { |
|
138 | + $tokenTypes[$patternInfo['token']] = $pos; |
|
139 | + } |
|
140 | + } |
|
141 | + } |
|
142 | + |
|
143 | + return $tokenTypes; |
|
144 | + |
|
145 | + }//end getPatternTokenTypes() |
|
146 | + |
|
147 | + |
|
148 | + /** |
|
149 | + * Returns the position in the pattern that this test should register as |
|
150 | + * a listener for the pattern. |
|
151 | + * |
|
152 | + * @param array $pattern The pattern to acquire the listener for. |
|
153 | + * |
|
154 | + * @return int The position in the pattern that this test should register |
|
155 | + * as the listener. |
|
156 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for. |
|
157 | + */ |
|
158 | + private function getListenerTokenPos($pattern) |
|
159 | + { |
|
160 | + $tokenTypes = $this->getPatternTokenTypes($pattern); |
|
161 | + $tokenCodes = array_keys($tokenTypes); |
|
162 | + $token = Tokens::getHighestWeightedToken($tokenCodes); |
|
163 | + |
|
164 | + // If we could not get a token. |
|
165 | + if ($token === false) { |
|
166 | + $error = 'Could not determine a token to listen for'; |
|
167 | + throw new RuntimeException($error); |
|
168 | + } |
|
169 | + |
|
170 | + return $tokenTypes[$token]; |
|
171 | + |
|
172 | + }//end getListenerTokenPos() |
|
173 | + |
|
174 | + |
|
175 | + /** |
|
176 | + * Processes the test. |
|
177 | + * |
|
178 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
179 | + * token occurred. |
|
180 | + * @param int $stackPtr The position in the tokens stack |
|
181 | + * where the listening token type |
|
182 | + * was found. |
|
183 | + * |
|
184 | + * @return void |
|
185 | + * @see register() |
|
186 | + */ |
|
187 | + final public function process(File $phpcsFile, $stackPtr) |
|
188 | + { |
|
189 | + $file = $phpcsFile->getFilename(); |
|
190 | + if ($this->currFile !== $file) { |
|
191 | + // We have changed files, so clean up. |
|
192 | + $this->errorPos = []; |
|
193 | + $this->currFile = $file; |
|
194 | + } |
|
195 | + |
|
196 | + $tokens = $phpcsFile->getTokens(); |
|
197 | + |
|
198 | + if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) { |
|
199 | + $this->processSupplementary($phpcsFile, $stackPtr); |
|
200 | + } |
|
201 | + |
|
202 | + $type = $tokens[$stackPtr]['code']; |
|
203 | + |
|
204 | + // If the type is not set, then it must have been a token registered |
|
205 | + // with registerSupplementary(). |
|
206 | + if (isset($this->parsedPatterns[$type]) === false) { |
|
207 | + return; |
|
208 | + } |
|
209 | + |
|
210 | + $allErrors = []; |
|
211 | + |
|
212 | + // Loop over each pattern that is listening to the current token type |
|
213 | + // that we are processing. |
|
214 | + foreach ($this->parsedPatterns[$type] as $patternInfo) { |
|
215 | + // If processPattern returns false, then the pattern that we are |
|
216 | + // checking the code with must not be designed to check that code. |
|
217 | + $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); |
|
218 | + if ($errors === false) { |
|
219 | + // The pattern didn't match. |
|
220 | + continue; |
|
221 | + } else if (empty($errors) === true) { |
|
222 | + // The pattern matched, but there were no errors. |
|
223 | + break; |
|
224 | + } |
|
225 | + |
|
226 | + foreach ($errors as $stackPtr => $error) { |
|
227 | + if (isset($this->errorPos[$stackPtr]) === false) { |
|
228 | + $this->errorPos[$stackPtr] = true; |
|
229 | + $allErrors[$stackPtr] = $error; |
|
230 | + } |
|
231 | + } |
|
232 | + } |
|
233 | + |
|
234 | + foreach ($allErrors as $stackPtr => $error) { |
|
235 | + $phpcsFile->addError($error, $stackPtr, 'Found'); |
|
236 | + } |
|
237 | + |
|
238 | + }//end process() |
|
239 | + |
|
240 | + |
|
241 | + /** |
|
242 | + * Processes the pattern and verifies the code at $stackPtr. |
|
243 | + * |
|
244 | + * @param array $patternInfo Information about the pattern used |
|
245 | + * for checking, which includes are |
|
246 | + * parsed token representation of the |
|
247 | + * pattern. |
|
248 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
249 | + * token occurred. |
|
250 | + * @param int $stackPtr The position in the tokens stack where |
|
251 | + * the listening token type was found. |
|
252 | + * |
|
253 | + * @return array |
|
254 | + */ |
|
255 | + protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) |
|
256 | + { |
|
257 | + $tokens = $phpcsFile->getTokens(); |
|
258 | + $pattern = $patternInfo['pattern']; |
|
259 | + $patternCode = $patternInfo['pattern_code']; |
|
260 | + $errors = []; |
|
261 | + $found = ''; |
|
262 | + |
|
263 | + $ignoreTokens = [T_WHITESPACE => T_WHITESPACE]; |
|
264 | + if ($this->ignoreComments === true) { |
|
265 | + $ignoreTokens += Tokens::$commentTokens; |
|
266 | + } |
|
267 | + |
|
268 | + $origStackPtr = $stackPtr; |
|
269 | + $hasError = false; |
|
270 | + |
|
271 | + if ($patternInfo['listen_pos'] > 0) { |
|
272 | + $stackPtr--; |
|
273 | + |
|
274 | + for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { |
|
275 | + if ($pattern[$i]['type'] === 'token') { |
|
276 | + if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
277 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
278 | + $found = $tokens[$stackPtr]['content'].$found; |
|
279 | + } |
|
280 | + |
|
281 | + // Only check the size of the whitespace if this is not |
|
282 | + // the first token. We don't care about the size of |
|
283 | + // leading whitespace, just that there is some. |
|
284 | + if ($i !== 0) { |
|
285 | + if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { |
|
286 | + $hasError = true; |
|
287 | + } |
|
288 | + } |
|
289 | + } else { |
|
290 | + // Check to see if this important token is the same as the |
|
291 | + // previous important token in the pattern. If it is not, |
|
292 | + // then the pattern cannot be for this piece of code. |
|
293 | + $prev = $phpcsFile->findPrevious( |
|
294 | + $ignoreTokens, |
|
295 | + $stackPtr, |
|
296 | + null, |
|
297 | + true |
|
298 | + ); |
|
299 | + |
|
300 | + if ($prev === false |
|
301 | + || $tokens[$prev]['code'] !== $pattern[$i]['token'] |
|
302 | + ) { |
|
303 | + return false; |
|
304 | + } |
|
305 | + |
|
306 | + // If we skipped past some whitespace tokens, then add them |
|
307 | + // to the found string. |
|
308 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
309 | + ($prev + 1), |
|
310 | + ($stackPtr - $prev - 1) |
|
311 | + ); |
|
312 | + |
|
313 | + $found = $tokens[$prev]['content'].$tokenContent.$found; |
|
314 | + |
|
315 | + if (isset($pattern[($i - 1)]) === true |
|
316 | + && $pattern[($i - 1)]['type'] === 'skip' |
|
317 | + ) { |
|
318 | + $stackPtr = $prev; |
|
319 | + } else { |
|
320 | + $stackPtr = ($prev - 1); |
|
321 | + } |
|
322 | + }//end if |
|
323 | + } else if ($pattern[$i]['type'] === 'skip') { |
|
324 | + // Skip to next piece of relevant code. |
|
325 | + if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
326 | + $to = 'parenthesis_opener'; |
|
327 | + } else { |
|
328 | + $to = 'scope_opener'; |
|
329 | + } |
|
330 | + |
|
331 | + // Find the previous opener. |
|
332 | + $next = $phpcsFile->findPrevious( |
|
333 | + $ignoreTokens, |
|
334 | + $stackPtr, |
|
335 | + null, |
|
336 | + true |
|
337 | + ); |
|
338 | + |
|
339 | + if ($next === false || isset($tokens[$next][$to]) === false) { |
|
340 | + // If there was not opener, then we must be |
|
341 | + // using the wrong pattern. |
|
342 | + return false; |
|
343 | + } |
|
344 | + |
|
345 | + if ($to === 'parenthesis_opener') { |
|
346 | + $found = '{'.$found; |
|
347 | + } else { |
|
348 | + $found = '('.$found; |
|
349 | + } |
|
350 | + |
|
351 | + $found = '...'.$found; |
|
352 | + |
|
353 | + // Skip to the opening token. |
|
354 | + $stackPtr = ($tokens[$next][$to] - 1); |
|
355 | + } else if ($pattern[$i]['type'] === 'string') { |
|
356 | + $found = 'abc'; |
|
357 | + } else if ($pattern[$i]['type'] === 'newline') { |
|
358 | + if ($this->ignoreComments === true |
|
359 | + && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true |
|
360 | + ) { |
|
361 | + $startComment = $phpcsFile->findPrevious( |
|
362 | + Tokens::$commentTokens, |
|
363 | + ($stackPtr - 1), |
|
364 | + null, |
|
365 | + true |
|
366 | + ); |
|
367 | + |
|
368 | + if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) { |
|
369 | + $startComment++; |
|
370 | + } |
|
371 | + |
|
372 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
373 | + $startComment, |
|
374 | + ($stackPtr - $startComment + 1) |
|
375 | + ); |
|
376 | + |
|
377 | + $found = $tokenContent.$found; |
|
378 | + $stackPtr = ($startComment - 1); |
|
379 | + } |
|
380 | + |
|
381 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
382 | + if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { |
|
383 | + $found = $tokens[$stackPtr]['content'].$found; |
|
384 | + |
|
385 | + // This may just be an indent that comes after a newline |
|
386 | + // so check the token before to make sure. If it is a newline, we |
|
387 | + // can ignore the error here. |
|
388 | + if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar) |
|
389 | + && ($this->ignoreComments === true |
|
390 | + && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false) |
|
391 | + ) { |
|
392 | + $hasError = true; |
|
393 | + } else { |
|
394 | + $stackPtr--; |
|
395 | + } |
|
396 | + } else { |
|
397 | + $found = 'EOL'.$found; |
|
398 | + } |
|
399 | + } else { |
|
400 | + $found = $tokens[$stackPtr]['content'].$found; |
|
401 | + $hasError = true; |
|
402 | + }//end if |
|
403 | + |
|
404 | + if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') { |
|
405 | + // Make sure they only have 1 newline. |
|
406 | + $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true); |
|
407 | + if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { |
|
408 | + $hasError = true; |
|
409 | + } |
|
410 | + } |
|
411 | + }//end if |
|
412 | + }//end for |
|
413 | + }//end if |
|
414 | + |
|
415 | + $stackPtr = $origStackPtr; |
|
416 | + $lastAddedStackPtr = null; |
|
417 | + $patternLen = count($pattern); |
|
418 | + |
|
419 | + for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { |
|
420 | + if (isset($tokens[$stackPtr]) === false) { |
|
421 | + break; |
|
422 | + } |
|
423 | + |
|
424 | + if ($pattern[$i]['type'] === 'token') { |
|
425 | + if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
426 | + if ($this->ignoreComments === true) { |
|
427 | + // If we are ignoring comments, check to see if this current |
|
428 | + // token is a comment. If so skip it. |
|
429 | + if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { |
|
430 | + continue; |
|
431 | + } |
|
432 | + |
|
433 | + // If the next token is a comment, the we need to skip the |
|
434 | + // current token as we should allow a space before a |
|
435 | + // comment for readability. |
|
436 | + if (isset($tokens[($stackPtr + 1)]) === true |
|
437 | + && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true |
|
438 | + ) { |
|
439 | + continue; |
|
440 | + } |
|
441 | + } |
|
442 | + |
|
443 | + $tokenContent = ''; |
|
444 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
445 | + if (isset($pattern[($i + 1)]) === false) { |
|
446 | + // This is the last token in the pattern, so just compare |
|
447 | + // the next token of content. |
|
448 | + $tokenContent = $tokens[$stackPtr]['content']; |
|
449 | + } else { |
|
450 | + // Get all the whitespace to the next token. |
|
451 | + $next = $phpcsFile->findNext( |
|
452 | + Tokens::$emptyTokens, |
|
453 | + $stackPtr, |
|
454 | + null, |
|
455 | + true |
|
456 | + ); |
|
457 | + |
|
458 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
459 | + $stackPtr, |
|
460 | + ($next - $stackPtr) |
|
461 | + ); |
|
462 | + |
|
463 | + $lastAddedStackPtr = $stackPtr; |
|
464 | + $stackPtr = $next; |
|
465 | + }//end if |
|
466 | + |
|
467 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
468 | + $found .= $tokenContent; |
|
469 | + } |
|
470 | + } else { |
|
471 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
472 | + $found .= $tokens[$stackPtr]['content']; |
|
473 | + $lastAddedStackPtr = $stackPtr; |
|
474 | + } |
|
475 | + }//end if |
|
476 | + |
|
477 | + if (isset($pattern[($i + 1)]) === true |
|
478 | + && $pattern[($i + 1)]['type'] === 'skip' |
|
479 | + ) { |
|
480 | + // The next token is a skip token, so we just need to make |
|
481 | + // sure the whitespace we found has *at least* the |
|
482 | + // whitespace required. |
|
483 | + if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { |
|
484 | + $hasError = true; |
|
485 | + } |
|
486 | + } else { |
|
487 | + if ($tokenContent !== $pattern[$i]['value']) { |
|
488 | + $hasError = true; |
|
489 | + } |
|
490 | + } |
|
491 | + } else { |
|
492 | + // Check to see if this important token is the same as the |
|
493 | + // next important token in the pattern. If it is not, then |
|
494 | + // the pattern cannot be for this piece of code. |
|
495 | + $next = $phpcsFile->findNext( |
|
496 | + $ignoreTokens, |
|
497 | + $stackPtr, |
|
498 | + null, |
|
499 | + true |
|
500 | + ); |
|
501 | + |
|
502 | + if ($next === false |
|
503 | + || $tokens[$next]['code'] !== $pattern[$i]['token'] |
|
504 | + ) { |
|
505 | + // The next important token did not match the pattern. |
|
506 | + return false; |
|
507 | + } |
|
508 | + |
|
509 | + if ($lastAddedStackPtr !== null) { |
|
510 | + if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET |
|
511 | + || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) |
|
512 | + && isset($tokens[$next]['scope_condition']) === true |
|
513 | + && $tokens[$next]['scope_condition'] > $lastAddedStackPtr |
|
514 | + ) { |
|
515 | + // This is a brace, but the owner of it is after the current |
|
516 | + // token, which means it does not belong to any token in |
|
517 | + // our pattern. This means the pattern is not for us. |
|
518 | + return false; |
|
519 | + } |
|
520 | + |
|
521 | + if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS |
|
522 | + || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) |
|
523 | + && isset($tokens[$next]['parenthesis_owner']) === true |
|
524 | + && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr |
|
525 | + ) { |
|
526 | + // This is a bracket, but the owner of it is after the current |
|
527 | + // token, which means it does not belong to any token in |
|
528 | + // our pattern. This means the pattern is not for us. |
|
529 | + return false; |
|
530 | + } |
|
531 | + }//end if |
|
532 | + |
|
533 | + // If we skipped past some whitespace tokens, then add them |
|
534 | + // to the found string. |
|
535 | + if (($next - $stackPtr) > 0) { |
|
536 | + $hasComment = false; |
|
537 | + for ($j = $stackPtr; $j < $next; $j++) { |
|
538 | + $found .= $tokens[$j]['content']; |
|
539 | + if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) { |
|
540 | + $hasComment = true; |
|
541 | + } |
|
542 | + } |
|
543 | + |
|
544 | + // If we are not ignoring comments, this additional |
|
545 | + // whitespace or comment is not allowed. If we are |
|
546 | + // ignoring comments, there needs to be at least one |
|
547 | + // comment for this to be allowed. |
|
548 | + if ($this->ignoreComments === false |
|
549 | + || ($this->ignoreComments === true |
|
550 | + && $hasComment === false) |
|
551 | + ) { |
|
552 | + $hasError = true; |
|
553 | + } |
|
554 | + |
|
555 | + // Even when ignoring comments, we are not allowed to include |
|
556 | + // newlines without the pattern specifying them, so |
|
557 | + // everything should be on the same line. |
|
558 | + if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { |
|
559 | + $hasError = true; |
|
560 | + } |
|
561 | + }//end if |
|
562 | + |
|
563 | + if ($next !== $lastAddedStackPtr) { |
|
564 | + $found .= $tokens[$next]['content']; |
|
565 | + $lastAddedStackPtr = $next; |
|
566 | + } |
|
567 | + |
|
568 | + if (isset($pattern[($i + 1)]) === true |
|
569 | + && $pattern[($i + 1)]['type'] === 'skip' |
|
570 | + ) { |
|
571 | + $stackPtr = $next; |
|
572 | + } else { |
|
573 | + $stackPtr = ($next + 1); |
|
574 | + } |
|
575 | + }//end if |
|
576 | + } else if ($pattern[$i]['type'] === 'skip') { |
|
577 | + if ($pattern[$i]['to'] === 'unknown') { |
|
578 | + $next = $phpcsFile->findNext( |
|
579 | + $pattern[($i + 1)]['token'], |
|
580 | + $stackPtr |
|
581 | + ); |
|
582 | + |
|
583 | + if ($next === false) { |
|
584 | + // Couldn't find the next token, so we must |
|
585 | + // be using the wrong pattern. |
|
586 | + return false; |
|
587 | + } |
|
588 | + |
|
589 | + $found .= '...'; |
|
590 | + $stackPtr = $next; |
|
591 | + } else { |
|
592 | + // Find the previous opener. |
|
593 | + $next = $phpcsFile->findPrevious( |
|
594 | + Tokens::$blockOpeners, |
|
595 | + $stackPtr |
|
596 | + ); |
|
597 | + |
|
598 | + if ($next === false |
|
599 | + || isset($tokens[$next][$pattern[$i]['to']]) === false |
|
600 | + ) { |
|
601 | + // If there was not opener, then we must |
|
602 | + // be using the wrong pattern. |
|
603 | + return false; |
|
604 | + } |
|
605 | + |
|
606 | + $found .= '...'; |
|
607 | + if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
608 | + $found .= ')'; |
|
609 | + } else { |
|
610 | + $found .= '}'; |
|
611 | + } |
|
612 | + |
|
613 | + // Skip to the closing token. |
|
614 | + $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); |
|
615 | + }//end if |
|
616 | + } else if ($pattern[$i]['type'] === 'string') { |
|
617 | + if ($tokens[$stackPtr]['code'] !== T_STRING) { |
|
618 | + $hasError = true; |
|
619 | + } |
|
620 | + |
|
621 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
622 | + $found .= 'abc'; |
|
623 | + $lastAddedStackPtr = $stackPtr; |
|
624 | + } |
|
625 | + |
|
626 | + $stackPtr++; |
|
627 | + } else if ($pattern[$i]['type'] === 'newline') { |
|
628 | + // Find the next token that contains a newline character. |
|
629 | + $newline = 0; |
|
630 | + for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { |
|
631 | + if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { |
|
632 | + $newline = $j; |
|
633 | + break; |
|
634 | + } |
|
635 | + } |
|
636 | + |
|
637 | + if ($newline === 0) { |
|
638 | + // We didn't find a newline character in the rest of the file. |
|
639 | + $next = ($phpcsFile->numTokens - 1); |
|
640 | + $hasError = true; |
|
641 | + } else { |
|
642 | + if ($this->ignoreComments === false) { |
|
643 | + // The newline character cannot be part of a comment. |
|
644 | + if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { |
|
645 | + $hasError = true; |
|
646 | + } |
|
647 | + } |
|
648 | + |
|
649 | + if ($newline === $stackPtr) { |
|
650 | + $next = ($stackPtr + 1); |
|
651 | + } else { |
|
652 | + // Check that there were no significant tokens that we |
|
653 | + // skipped over to find our newline character. |
|
654 | + $next = $phpcsFile->findNext( |
|
655 | + $ignoreTokens, |
|
656 | + $stackPtr, |
|
657 | + null, |
|
658 | + true |
|
659 | + ); |
|
660 | + |
|
661 | + if ($next < $newline) { |
|
662 | + // We skipped a non-ignored token. |
|
663 | + $hasError = true; |
|
664 | + } else { |
|
665 | + $next = ($newline + 1); |
|
666 | + } |
|
667 | + } |
|
668 | + }//end if |
|
669 | + |
|
670 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
671 | + $found .= $phpcsFile->getTokensAsString( |
|
672 | + $stackPtr, |
|
673 | + ($next - $stackPtr) |
|
674 | + ); |
|
675 | + |
|
676 | + $lastAddedStackPtr = ($next - 1); |
|
677 | + } |
|
678 | + |
|
679 | + $stackPtr = $next; |
|
680 | + }//end if |
|
681 | + }//end for |
|
682 | + |
|
683 | + if ($hasError === true) { |
|
684 | + $error = $this->prepareError($found, $patternCode); |
|
685 | + $errors[$origStackPtr] = $error; |
|
686 | + } |
|
687 | + |
|
688 | + return $errors; |
|
689 | + |
|
690 | + }//end processPattern() |
|
691 | + |
|
692 | + |
|
693 | + /** |
|
694 | + * Prepares an error for the specified patternCode. |
|
695 | + * |
|
696 | + * @param string $found The actual found string in the code. |
|
697 | + * @param string $patternCode The expected pattern code. |
|
698 | + * |
|
699 | + * @return string The error message. |
|
700 | + */ |
|
701 | + protected function prepareError($found, $patternCode) |
|
702 | + { |
|
703 | + $found = str_replace("\r\n", '\n', $found); |
|
704 | + $found = str_replace("\n", '\n', $found); |
|
705 | + $found = str_replace("\r", '\n', $found); |
|
706 | + $found = str_replace("\t", '\t', $found); |
|
707 | + $found = str_replace('EOL', '\n', $found); |
|
708 | + $expected = str_replace('EOL', '\n', $patternCode); |
|
709 | + |
|
710 | + $error = "Expected \"$expected\"; found \"$found\""; |
|
711 | + |
|
712 | + return $error; |
|
713 | + |
|
714 | + }//end prepareError() |
|
715 | + |
|
716 | + |
|
717 | + /** |
|
718 | + * Returns the patterns that should be checked. |
|
719 | + * |
|
720 | + * @return string[] |
|
721 | + */ |
|
722 | + abstract protected function getPatterns(); |
|
723 | + |
|
724 | + |
|
725 | + /** |
|
726 | + * Registers any supplementary tokens that this test might wish to process. |
|
727 | + * |
|
728 | + * A sniff may wish to register supplementary tests when it wishes to group |
|
729 | + * an arbitrary validation that cannot be performed using a pattern, with |
|
730 | + * other pattern tests. |
|
731 | + * |
|
732 | + * @return int[] |
|
733 | + * @see processSupplementary() |
|
734 | + */ |
|
735 | + protected function registerSupplementary() |
|
736 | + { |
|
737 | + return []; |
|
738 | + |
|
739 | + }//end registerSupplementary() |
|
740 | + |
|
741 | + |
|
742 | + /** |
|
743 | + * Processes any tokens registered with registerSupplementary(). |
|
744 | + * |
|
745 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to |
|
746 | + * process the skip. |
|
747 | + * @param int $stackPtr The position in the tokens stack to |
|
748 | + * process. |
|
749 | + * |
|
750 | + * @return void |
|
751 | + * @see registerSupplementary() |
|
752 | + */ |
|
753 | + protected function processSupplementary(File $phpcsFile, $stackPtr) |
|
754 | + { |
|
755 | + |
|
756 | + }//end processSupplementary() |
|
757 | + |
|
758 | + |
|
759 | + /** |
|
760 | + * Parses a pattern string into an array of pattern steps. |
|
761 | + * |
|
762 | + * @param string $pattern The pattern to parse. |
|
763 | + * |
|
764 | + * @return array The parsed pattern array. |
|
765 | + * @see createSkipPattern() |
|
766 | + * @see createTokenPattern() |
|
767 | + */ |
|
768 | + private function parse($pattern) |
|
769 | + { |
|
770 | + $patterns = []; |
|
771 | + $length = strlen($pattern); |
|
772 | + $lastToken = 0; |
|
773 | + $firstToken = 0; |
|
774 | + |
|
775 | + for ($i = 0; $i < $length; $i++) { |
|
776 | + $specialPattern = false; |
|
777 | + $isLastChar = ($i === ($length - 1)); |
|
778 | + $oldFirstToken = $firstToken; |
|
779 | + |
|
780 | + if (substr($pattern, $i, 3) === '...') { |
|
781 | + // It's a skip pattern. The skip pattern requires the |
|
782 | + // content of the token in the "from" position and the token |
|
783 | + // to skip to. |
|
784 | + $specialPattern = $this->createSkipPattern($pattern, ($i - 1)); |
|
785 | + $lastToken = ($i - $firstToken); |
|
786 | + $firstToken = ($i + 3); |
|
787 | + $i += 2; |
|
788 | + |
|
789 | + if ($specialPattern['to'] !== 'unknown') { |
|
790 | + $firstToken++; |
|
791 | + } |
|
792 | + } else if (substr($pattern, $i, 3) === 'abc') { |
|
793 | + $specialPattern = ['type' => 'string']; |
|
794 | + $lastToken = ($i - $firstToken); |
|
795 | + $firstToken = ($i + 3); |
|
796 | + $i += 2; |
|
797 | + } else if (substr($pattern, $i, 3) === 'EOL') { |
|
798 | + $specialPattern = ['type' => 'newline']; |
|
799 | + $lastToken = ($i - $firstToken); |
|
800 | + $firstToken = ($i + 3); |
|
801 | + $i += 2; |
|
802 | + }//end if |
|
803 | + |
|
804 | + if ($specialPattern !== false || $isLastChar === true) { |
|
805 | + // If we are at the end of the string, don't worry about a limit. |
|
806 | + if ($isLastChar === true) { |
|
807 | + // Get the string from the end of the last skip pattern, if any, |
|
808 | + // to the end of the pattern string. |
|
809 | + $str = substr($pattern, $oldFirstToken); |
|
810 | + } else { |
|
811 | + // Get the string from the end of the last special pattern, |
|
812 | + // if any, to the start of this special pattern. |
|
813 | + if ($lastToken === 0) { |
|
814 | + // Note that if the last special token was zero characters ago, |
|
815 | + // there will be nothing to process so we can skip this bit. |
|
816 | + // This happens if you have something like: EOL... in your pattern. |
|
817 | + $str = ''; |
|
818 | + } else { |
|
819 | + $str = substr($pattern, $oldFirstToken, $lastToken); |
|
820 | + } |
|
821 | + } |
|
822 | + |
|
823 | + if ($str !== '') { |
|
824 | + $tokenPatterns = $this->createTokenPattern($str); |
|
825 | + foreach ($tokenPatterns as $tokenPattern) { |
|
826 | + $patterns[] = $tokenPattern; |
|
827 | + } |
|
828 | + } |
|
829 | + |
|
830 | + // Make sure we don't skip the last token. |
|
831 | + if ($isLastChar === false && $i === ($length - 1)) { |
|
832 | + $i--; |
|
833 | + } |
|
834 | + }//end if |
|
835 | + |
|
836 | + // Add the skip pattern *after* we have processed |
|
837 | + // all the tokens from the end of the last skip pattern |
|
838 | + // to the start of this skip pattern. |
|
839 | + if ($specialPattern !== false) { |
|
840 | + $patterns[] = $specialPattern; |
|
841 | + } |
|
842 | + }//end for |
|
843 | + |
|
844 | + return $patterns; |
|
845 | + |
|
846 | + }//end parse() |
|
847 | + |
|
848 | + |
|
849 | + /** |
|
850 | + * Creates a skip pattern. |
|
851 | + * |
|
852 | + * @param string $pattern The pattern being parsed. |
|
853 | + * @param string $from The token content that the skip pattern starts from. |
|
854 | + * |
|
855 | + * @return array The pattern step. |
|
856 | + * @see createTokenPattern() |
|
857 | + * @see parse() |
|
858 | + */ |
|
859 | + private function createSkipPattern($pattern, $from) |
|
860 | + { |
|
861 | + $skip = ['type' => 'skip']; |
|
862 | + |
|
863 | + $nestedParenthesis = 0; |
|
864 | + $nestedBraces = 0; |
|
865 | + for ($start = $from; $start >= 0; $start--) { |
|
866 | + switch ($pattern[$start]) { |
|
867 | + case '(': |
|
868 | + if ($nestedParenthesis === 0) { |
|
869 | + $skip['to'] = 'parenthesis_closer'; |
|
870 | + } |
|
871 | + |
|
872 | + $nestedParenthesis--; |
|
873 | + break; |
|
874 | + case '{': |
|
875 | + if ($nestedBraces === 0) { |
|
876 | + $skip['to'] = 'scope_closer'; |
|
877 | + } |
|
878 | + |
|
879 | + $nestedBraces--; |
|
880 | + break; |
|
881 | + case '}': |
|
882 | + $nestedBraces++; |
|
883 | + break; |
|
884 | + case ')': |
|
885 | + $nestedParenthesis++; |
|
886 | + break; |
|
887 | + }//end switch |
|
888 | + |
|
889 | + if (isset($skip['to']) === true) { |
|
890 | + break; |
|
891 | + } |
|
892 | + }//end for |
|
893 | + |
|
894 | + if (isset($skip['to']) === false) { |
|
895 | + $skip['to'] = 'unknown'; |
|
896 | + } |
|
897 | + |
|
898 | + return $skip; |
|
899 | + |
|
900 | + }//end createSkipPattern() |
|
901 | + |
|
902 | + |
|
903 | + /** |
|
904 | + * Creates a token pattern. |
|
905 | + * |
|
906 | + * @param string $str The tokens string that the pattern should match. |
|
907 | + * |
|
908 | + * @return array The pattern step. |
|
909 | + * @see createSkipPattern() |
|
910 | + * @see parse() |
|
911 | + */ |
|
912 | + private function createTokenPattern($str) |
|
913 | + { |
|
914 | + // Don't add a space after the closing php tag as it will add a new |
|
915 | + // whitespace token. |
|
916 | + $tokenizer = new PHP('<?php '.$str.'?>', null); |
|
917 | + |
|
918 | + // Remove the <?php tag from the front and the end php tag from the back. |
|
919 | + $tokens = $tokenizer->getTokens(); |
|
920 | + $tokens = array_slice($tokens, 1, (count($tokens) - 2)); |
|
921 | + |
|
922 | + $patterns = []; |
|
923 | + foreach ($tokens as $patternInfo) { |
|
924 | + $patterns[] = [ |
|
925 | + 'type' => 'token', |
|
926 | + 'token' => $patternInfo['code'], |
|
927 | + 'value' => $patternInfo['content'], |
|
928 | + ]; |
|
929 | + } |
|
930 | + |
|
931 | + return $patterns; |
|
932 | + |
|
933 | + }//end createTokenPattern() |
|
934 | 934 | |
935 | 935 | |
936 | 936 | }//end class |
@@ -16,219 +16,219 @@ |
||
16 | 16 | { |
17 | 17 | |
18 | 18 | |
19 | - /** |
|
20 | - * Returns an array of tokens this test wants to listen for. |
|
21 | - * |
|
22 | - * @return array |
|
23 | - */ |
|
24 | - final public function register() |
|
25 | - { |
|
26 | - return [ |
|
27 | - T_ARRAY, |
|
28 | - T_OPEN_SHORT_ARRAY, |
|
29 | - ]; |
|
30 | - |
|
31 | - }//end register() |
|
32 | - |
|
33 | - |
|
34 | - /** |
|
35 | - * Processes this sniff, when one of its tokens is encountered. |
|
36 | - * |
|
37 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
38 | - * @param int $stackPtr The position of the current token in |
|
39 | - * the stack passed in $tokens. |
|
40 | - * |
|
41 | - * @return void |
|
42 | - */ |
|
43 | - public function process(File $phpcsFile, $stackPtr) |
|
44 | - { |
|
45 | - $tokens = $phpcsFile->getTokens(); |
|
46 | - |
|
47 | - if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
48 | - $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no'); |
|
49 | - |
|
50 | - $arrayStart = $tokens[$stackPtr]['parenthesis_opener']; |
|
51 | - if (isset($tokens[$arrayStart]['parenthesis_closer']) === false) { |
|
52 | - // Incomplete array. |
|
53 | - return; |
|
54 | - } |
|
55 | - |
|
56 | - $arrayEnd = $tokens[$arrayStart]['parenthesis_closer']; |
|
57 | - } else { |
|
58 | - $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes'); |
|
59 | - $arrayStart = $stackPtr; |
|
60 | - $arrayEnd = $tokens[$stackPtr]['bracket_closer']; |
|
61 | - } |
|
62 | - |
|
63 | - $lastContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrayEnd - 1), null, true); |
|
64 | - if ($tokens[$lastContent]['code'] === T_COMMA) { |
|
65 | - // Last array item ends with a comma. |
|
66 | - $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes'); |
|
67 | - $lastArrayToken = $lastContent; |
|
68 | - } else { |
|
69 | - $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no'); |
|
70 | - $lastArrayToken = $arrayEnd; |
|
71 | - } |
|
72 | - |
|
73 | - if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
74 | - $lastToken = $tokens[$stackPtr]['parenthesis_opener']; |
|
75 | - } else { |
|
76 | - $lastToken = $stackPtr; |
|
77 | - } |
|
78 | - |
|
79 | - $keyUsed = false; |
|
80 | - $indices = []; |
|
81 | - |
|
82 | - for ($checkToken = ($stackPtr + 1); $checkToken <= $lastArrayToken; $checkToken++) { |
|
83 | - // Skip bracketed statements, like function calls. |
|
84 | - if ($tokens[$checkToken]['code'] === T_OPEN_PARENTHESIS |
|
85 | - && (isset($tokens[$checkToken]['parenthesis_owner']) === false |
|
86 | - || $tokens[$checkToken]['parenthesis_owner'] !== $stackPtr) |
|
87 | - ) { |
|
88 | - $checkToken = $tokens[$checkToken]['parenthesis_closer']; |
|
89 | - continue; |
|
90 | - } |
|
91 | - |
|
92 | - if ($tokens[$checkToken]['code'] === T_ARRAY |
|
93 | - || $tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY |
|
94 | - || $tokens[$checkToken]['code'] === T_CLOSURE |
|
95 | - ) { |
|
96 | - // Let subsequent calls of this test handle nested arrays. |
|
97 | - if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) { |
|
98 | - $indices[] = ['value_start' => $checkToken]; |
|
99 | - $lastToken = $checkToken; |
|
100 | - } |
|
101 | - |
|
102 | - if ($tokens[$checkToken]['code'] === T_ARRAY) { |
|
103 | - $checkToken = $tokens[$tokens[$checkToken]['parenthesis_opener']]['parenthesis_closer']; |
|
104 | - } else if ($tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY) { |
|
105 | - $checkToken = $tokens[$checkToken]['bracket_closer']; |
|
106 | - } else { |
|
107 | - // T_CLOSURE. |
|
108 | - $checkToken = $tokens[$checkToken]['scope_closer']; |
|
109 | - } |
|
110 | - |
|
111 | - $checkToken = $phpcsFile->findNext(T_WHITESPACE, ($checkToken + 1), null, true); |
|
112 | - $lastToken = $checkToken; |
|
113 | - if ($tokens[$checkToken]['code'] !== T_COMMA) { |
|
114 | - $checkToken--; |
|
115 | - } |
|
116 | - |
|
117 | - continue; |
|
118 | - }//end if |
|
119 | - |
|
120 | - if ($tokens[$checkToken]['code'] !== T_DOUBLE_ARROW |
|
121 | - && $tokens[$checkToken]['code'] !== T_COMMA |
|
122 | - && $checkToken !== $arrayEnd |
|
123 | - ) { |
|
124 | - continue; |
|
125 | - } |
|
126 | - |
|
127 | - if ($tokens[$checkToken]['code'] === T_COMMA |
|
128 | - || $checkToken === $arrayEnd |
|
129 | - ) { |
|
130 | - $stackPtrCount = 0; |
|
131 | - if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { |
|
132 | - $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']); |
|
133 | - } |
|
134 | - |
|
135 | - $commaCount = 0; |
|
136 | - if (isset($tokens[$checkToken]['nested_parenthesis']) === true) { |
|
137 | - $commaCount = count($tokens[$checkToken]['nested_parenthesis']); |
|
138 | - if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
139 | - // Remove parenthesis that are used to define the array. |
|
140 | - $commaCount--; |
|
141 | - } |
|
142 | - } |
|
143 | - |
|
144 | - if ($commaCount > $stackPtrCount) { |
|
145 | - // This comma is inside more parenthesis than the ARRAY keyword, |
|
146 | - // so it is actually a comma used to do things like |
|
147 | - // separate arguments in a function call. |
|
148 | - continue; |
|
149 | - } |
|
150 | - |
|
151 | - if ($keyUsed === false) { |
|
152 | - $valueContent = $phpcsFile->findNext( |
|
153 | - Tokens::$emptyTokens, |
|
154 | - ($lastToken + 1), |
|
155 | - $checkToken, |
|
156 | - true |
|
157 | - ); |
|
158 | - |
|
159 | - $indices[] = ['value_start' => $valueContent]; |
|
160 | - } |
|
161 | - |
|
162 | - $lastToken = $checkToken; |
|
163 | - $keyUsed = false; |
|
164 | - continue; |
|
165 | - }//end if |
|
166 | - |
|
167 | - if ($tokens[$checkToken]['code'] === T_DOUBLE_ARROW) { |
|
168 | - $keyUsed = true; |
|
169 | - |
|
170 | - // Find the start of index that uses this double arrow. |
|
171 | - $indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($checkToken - 1), $arrayStart, true); |
|
172 | - $indexStart = $phpcsFile->findStartOfStatement($indexEnd); |
|
173 | - |
|
174 | - // Find the value of this index. |
|
175 | - $nextContent = $phpcsFile->findNext( |
|
176 | - Tokens::$emptyTokens, |
|
177 | - ($checkToken + 1), |
|
178 | - $arrayEnd, |
|
179 | - true |
|
180 | - ); |
|
181 | - |
|
182 | - $indices[] = [ |
|
183 | - 'index_start' => $indexStart, |
|
184 | - 'index_end' => $indexEnd, |
|
185 | - 'arrow' => $checkToken, |
|
186 | - 'value_start' => $nextContent, |
|
187 | - ]; |
|
188 | - |
|
189 | - $lastToken = $checkToken; |
|
190 | - }//end if |
|
191 | - }//end for |
|
192 | - |
|
193 | - if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) { |
|
194 | - $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
195 | - } else { |
|
196 | - $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
197 | - } |
|
198 | - |
|
199 | - }//end process() |
|
200 | - |
|
201 | - |
|
202 | - /** |
|
203 | - * Processes a single-line array definition. |
|
204 | - * |
|
205 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
206 | - * @param int $stackPtr The position of the current token |
|
207 | - * in the stack passed in $tokens. |
|
208 | - * @param int $arrayStart The token that starts the array definition. |
|
209 | - * @param int $arrayEnd The token that ends the array definition. |
|
210 | - * @param array $indices An array of token positions for the array keys, |
|
211 | - * double arrows, and values. |
|
212 | - * |
|
213 | - * @return void |
|
214 | - */ |
|
215 | - abstract protected function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
216 | - |
|
217 | - |
|
218 | - /** |
|
219 | - * Processes a multi-line array definition. |
|
220 | - * |
|
221 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
222 | - * @param int $stackPtr The position of the current token |
|
223 | - * in the stack passed in $tokens. |
|
224 | - * @param int $arrayStart The token that starts the array definition. |
|
225 | - * @param int $arrayEnd The token that ends the array definition. |
|
226 | - * @param array $indices An array of token positions for the array keys, |
|
227 | - * double arrows, and values. |
|
228 | - * |
|
229 | - * @return void |
|
230 | - */ |
|
231 | - abstract protected function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
19 | + /** |
|
20 | + * Returns an array of tokens this test wants to listen for. |
|
21 | + * |
|
22 | + * @return array |
|
23 | + */ |
|
24 | + final public function register() |
|
25 | + { |
|
26 | + return [ |
|
27 | + T_ARRAY, |
|
28 | + T_OPEN_SHORT_ARRAY, |
|
29 | + ]; |
|
30 | + |
|
31 | + }//end register() |
|
32 | + |
|
33 | + |
|
34 | + /** |
|
35 | + * Processes this sniff, when one of its tokens is encountered. |
|
36 | + * |
|
37 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
38 | + * @param int $stackPtr The position of the current token in |
|
39 | + * the stack passed in $tokens. |
|
40 | + * |
|
41 | + * @return void |
|
42 | + */ |
|
43 | + public function process(File $phpcsFile, $stackPtr) |
|
44 | + { |
|
45 | + $tokens = $phpcsFile->getTokens(); |
|
46 | + |
|
47 | + if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
48 | + $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'no'); |
|
49 | + |
|
50 | + $arrayStart = $tokens[$stackPtr]['parenthesis_opener']; |
|
51 | + if (isset($tokens[$arrayStart]['parenthesis_closer']) === false) { |
|
52 | + // Incomplete array. |
|
53 | + return; |
|
54 | + } |
|
55 | + |
|
56 | + $arrayEnd = $tokens[$arrayStart]['parenthesis_closer']; |
|
57 | + } else { |
|
58 | + $phpcsFile->recordMetric($stackPtr, 'Short array syntax used', 'yes'); |
|
59 | + $arrayStart = $stackPtr; |
|
60 | + $arrayEnd = $tokens[$stackPtr]['bracket_closer']; |
|
61 | + } |
|
62 | + |
|
63 | + $lastContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrayEnd - 1), null, true); |
|
64 | + if ($tokens[$lastContent]['code'] === T_COMMA) { |
|
65 | + // Last array item ends with a comma. |
|
66 | + $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'yes'); |
|
67 | + $lastArrayToken = $lastContent; |
|
68 | + } else { |
|
69 | + $phpcsFile->recordMetric($stackPtr, 'Array end comma', 'no'); |
|
70 | + $lastArrayToken = $arrayEnd; |
|
71 | + } |
|
72 | + |
|
73 | + if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
74 | + $lastToken = $tokens[$stackPtr]['parenthesis_opener']; |
|
75 | + } else { |
|
76 | + $lastToken = $stackPtr; |
|
77 | + } |
|
78 | + |
|
79 | + $keyUsed = false; |
|
80 | + $indices = []; |
|
81 | + |
|
82 | + for ($checkToken = ($stackPtr + 1); $checkToken <= $lastArrayToken; $checkToken++) { |
|
83 | + // Skip bracketed statements, like function calls. |
|
84 | + if ($tokens[$checkToken]['code'] === T_OPEN_PARENTHESIS |
|
85 | + && (isset($tokens[$checkToken]['parenthesis_owner']) === false |
|
86 | + || $tokens[$checkToken]['parenthesis_owner'] !== $stackPtr) |
|
87 | + ) { |
|
88 | + $checkToken = $tokens[$checkToken]['parenthesis_closer']; |
|
89 | + continue; |
|
90 | + } |
|
91 | + |
|
92 | + if ($tokens[$checkToken]['code'] === T_ARRAY |
|
93 | + || $tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY |
|
94 | + || $tokens[$checkToken]['code'] === T_CLOSURE |
|
95 | + ) { |
|
96 | + // Let subsequent calls of this test handle nested arrays. |
|
97 | + if ($tokens[$lastToken]['code'] !== T_DOUBLE_ARROW) { |
|
98 | + $indices[] = ['value_start' => $checkToken]; |
|
99 | + $lastToken = $checkToken; |
|
100 | + } |
|
101 | + |
|
102 | + if ($tokens[$checkToken]['code'] === T_ARRAY) { |
|
103 | + $checkToken = $tokens[$tokens[$checkToken]['parenthesis_opener']]['parenthesis_closer']; |
|
104 | + } else if ($tokens[$checkToken]['code'] === T_OPEN_SHORT_ARRAY) { |
|
105 | + $checkToken = $tokens[$checkToken]['bracket_closer']; |
|
106 | + } else { |
|
107 | + // T_CLOSURE. |
|
108 | + $checkToken = $tokens[$checkToken]['scope_closer']; |
|
109 | + } |
|
110 | + |
|
111 | + $checkToken = $phpcsFile->findNext(T_WHITESPACE, ($checkToken + 1), null, true); |
|
112 | + $lastToken = $checkToken; |
|
113 | + if ($tokens[$checkToken]['code'] !== T_COMMA) { |
|
114 | + $checkToken--; |
|
115 | + } |
|
116 | + |
|
117 | + continue; |
|
118 | + }//end if |
|
119 | + |
|
120 | + if ($tokens[$checkToken]['code'] !== T_DOUBLE_ARROW |
|
121 | + && $tokens[$checkToken]['code'] !== T_COMMA |
|
122 | + && $checkToken !== $arrayEnd |
|
123 | + ) { |
|
124 | + continue; |
|
125 | + } |
|
126 | + |
|
127 | + if ($tokens[$checkToken]['code'] === T_COMMA |
|
128 | + || $checkToken === $arrayEnd |
|
129 | + ) { |
|
130 | + $stackPtrCount = 0; |
|
131 | + if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { |
|
132 | + $stackPtrCount = count($tokens[$stackPtr]['nested_parenthesis']); |
|
133 | + } |
|
134 | + |
|
135 | + $commaCount = 0; |
|
136 | + if (isset($tokens[$checkToken]['nested_parenthesis']) === true) { |
|
137 | + $commaCount = count($tokens[$checkToken]['nested_parenthesis']); |
|
138 | + if ($tokens[$stackPtr]['code'] === T_ARRAY) { |
|
139 | + // Remove parenthesis that are used to define the array. |
|
140 | + $commaCount--; |
|
141 | + } |
|
142 | + } |
|
143 | + |
|
144 | + if ($commaCount > $stackPtrCount) { |
|
145 | + // This comma is inside more parenthesis than the ARRAY keyword, |
|
146 | + // so it is actually a comma used to do things like |
|
147 | + // separate arguments in a function call. |
|
148 | + continue; |
|
149 | + } |
|
150 | + |
|
151 | + if ($keyUsed === false) { |
|
152 | + $valueContent = $phpcsFile->findNext( |
|
153 | + Tokens::$emptyTokens, |
|
154 | + ($lastToken + 1), |
|
155 | + $checkToken, |
|
156 | + true |
|
157 | + ); |
|
158 | + |
|
159 | + $indices[] = ['value_start' => $valueContent]; |
|
160 | + } |
|
161 | + |
|
162 | + $lastToken = $checkToken; |
|
163 | + $keyUsed = false; |
|
164 | + continue; |
|
165 | + }//end if |
|
166 | + |
|
167 | + if ($tokens[$checkToken]['code'] === T_DOUBLE_ARROW) { |
|
168 | + $keyUsed = true; |
|
169 | + |
|
170 | + // Find the start of index that uses this double arrow. |
|
171 | + $indexEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($checkToken - 1), $arrayStart, true); |
|
172 | + $indexStart = $phpcsFile->findStartOfStatement($indexEnd); |
|
173 | + |
|
174 | + // Find the value of this index. |
|
175 | + $nextContent = $phpcsFile->findNext( |
|
176 | + Tokens::$emptyTokens, |
|
177 | + ($checkToken + 1), |
|
178 | + $arrayEnd, |
|
179 | + true |
|
180 | + ); |
|
181 | + |
|
182 | + $indices[] = [ |
|
183 | + 'index_start' => $indexStart, |
|
184 | + 'index_end' => $indexEnd, |
|
185 | + 'arrow' => $checkToken, |
|
186 | + 'value_start' => $nextContent, |
|
187 | + ]; |
|
188 | + |
|
189 | + $lastToken = $checkToken; |
|
190 | + }//end if |
|
191 | + }//end for |
|
192 | + |
|
193 | + if ($tokens[$arrayStart]['line'] === $tokens[$arrayEnd]['line']) { |
|
194 | + $this->processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
195 | + } else { |
|
196 | + $this->processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
197 | + } |
|
198 | + |
|
199 | + }//end process() |
|
200 | + |
|
201 | + |
|
202 | + /** |
|
203 | + * Processes a single-line array definition. |
|
204 | + * |
|
205 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
206 | + * @param int $stackPtr The position of the current token |
|
207 | + * in the stack passed in $tokens. |
|
208 | + * @param int $arrayStart The token that starts the array definition. |
|
209 | + * @param int $arrayEnd The token that ends the array definition. |
|
210 | + * @param array $indices An array of token positions for the array keys, |
|
211 | + * double arrows, and values. |
|
212 | + * |
|
213 | + * @return void |
|
214 | + */ |
|
215 | + abstract protected function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
216 | + |
|
217 | + |
|
218 | + /** |
|
219 | + * Processes a multi-line array definition. |
|
220 | + * |
|
221 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked. |
|
222 | + * @param int $stackPtr The position of the current token |
|
223 | + * in the stack passed in $tokens. |
|
224 | + * @param int $arrayStart The token that starts the array definition. |
|
225 | + * @param int $arrayEnd The token that ends the array definition. |
|
226 | + * @param array $indices An array of token positions for the array keys, |
|
227 | + * double arrows, and values. |
|
228 | + * |
|
229 | + * @return void |
|
230 | + */ |
|
231 | + abstract protected function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices); |
|
232 | 232 | |
233 | 233 | |
234 | 234 | }//end class |
@@ -22,210 +22,210 @@ |
||
22 | 22 | { |
23 | 23 | |
24 | 24 | |
25 | - /** |
|
26 | - * List of PHP Reserved variables. |
|
27 | - * |
|
28 | - * Used by various naming convention sniffs. |
|
29 | - * |
|
30 | - * @var array |
|
31 | - */ |
|
32 | - protected $phpReservedVars = [ |
|
33 | - '_SERVER' => true, |
|
34 | - '_GET' => true, |
|
35 | - '_POST' => true, |
|
36 | - '_REQUEST' => true, |
|
37 | - '_SESSION' => true, |
|
38 | - '_ENV' => true, |
|
39 | - '_COOKIE' => true, |
|
40 | - '_FILES' => true, |
|
41 | - 'GLOBALS' => true, |
|
42 | - 'http_response_header' => true, |
|
43 | - 'HTTP_RAW_POST_DATA' => true, |
|
44 | - 'php_errormsg' => true, |
|
45 | - ]; |
|
46 | - |
|
47 | - |
|
48 | - /** |
|
49 | - * Constructs an AbstractVariableTest. |
|
50 | - */ |
|
51 | - public function __construct() |
|
52 | - { |
|
53 | - $scopes = Tokens::$ooScopeTokens; |
|
54 | - |
|
55 | - $listen = [ |
|
56 | - T_VARIABLE, |
|
57 | - T_DOUBLE_QUOTED_STRING, |
|
58 | - T_HEREDOC, |
|
59 | - ]; |
|
60 | - |
|
61 | - parent::__construct($scopes, $listen, true); |
|
62 | - |
|
63 | - }//end __construct() |
|
64 | - |
|
65 | - |
|
66 | - /** |
|
67 | - * Processes the token in the specified PHP_CodeSniffer\Files\File. |
|
68 | - * |
|
69 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
70 | - * token was found. |
|
71 | - * @param int $stackPtr The position where the token was found. |
|
72 | - * @param int $currScope The current scope opener token. |
|
73 | - * |
|
74 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
75 | - * called again on the current file until the returned stack |
|
76 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
77 | - * the rest of the file. |
|
78 | - */ |
|
79 | - final protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) |
|
80 | - { |
|
81 | - $tokens = $phpcsFile->getTokens(); |
|
82 | - |
|
83 | - if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING |
|
84 | - || $tokens[$stackPtr]['code'] === T_HEREDOC |
|
85 | - ) { |
|
86 | - // Check to see if this string has a variable in it. |
|
87 | - $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|'; |
|
88 | - if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) { |
|
89 | - return $this->processVariableInString($phpcsFile, $stackPtr); |
|
90 | - } |
|
91 | - |
|
92 | - return; |
|
93 | - } |
|
94 | - |
|
95 | - // If this token is nested inside a function at a deeper |
|
96 | - // level than the current OO scope that was found, it's a normal |
|
97 | - // variable and not a member var. |
|
98 | - $conditions = array_reverse($tokens[$stackPtr]['conditions'], true); |
|
99 | - $inFunction = false; |
|
100 | - foreach ($conditions as $scope => $code) { |
|
101 | - if (isset(Tokens::$ooScopeTokens[$code]) === true) { |
|
102 | - break; |
|
103 | - } |
|
104 | - |
|
105 | - if ($code === T_FUNCTION || $code === T_CLOSURE) { |
|
106 | - $inFunction = true; |
|
107 | - } |
|
108 | - } |
|
109 | - |
|
110 | - if ($scope !== $currScope) { |
|
111 | - // We found a closer scope to this token, so ignore |
|
112 | - // this particular time through the sniff. We will process |
|
113 | - // this token when this closer scope is found to avoid |
|
114 | - // duplicate checks. |
|
115 | - return; |
|
116 | - } |
|
117 | - |
|
118 | - // Just make sure this isn't a variable in a function declaration. |
|
119 | - if ($inFunction === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) { |
|
120 | - foreach ($tokens[$stackPtr]['nested_parenthesis'] as $opener => $closer) { |
|
121 | - if (isset($tokens[$opener]['parenthesis_owner']) === false) { |
|
122 | - // Check if this is a USE statement for a closure. |
|
123 | - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true); |
|
124 | - if ($tokens[$prev]['code'] === T_USE) { |
|
125 | - $inFunction = true; |
|
126 | - break; |
|
127 | - } |
|
128 | - |
|
129 | - continue; |
|
130 | - } |
|
131 | - |
|
132 | - $owner = $tokens[$opener]['parenthesis_owner']; |
|
133 | - if ($tokens[$owner]['code'] === T_FUNCTION |
|
134 | - || $tokens[$owner]['code'] === T_CLOSURE |
|
135 | - ) { |
|
136 | - $inFunction = true; |
|
137 | - break; |
|
138 | - } |
|
139 | - } |
|
140 | - }//end if |
|
141 | - |
|
142 | - if ($inFunction === true) { |
|
143 | - return $this->processVariable($phpcsFile, $stackPtr); |
|
144 | - } else { |
|
145 | - return $this->processMemberVar($phpcsFile, $stackPtr); |
|
146 | - } |
|
147 | - |
|
148 | - }//end processTokenWithinScope() |
|
149 | - |
|
150 | - |
|
151 | - /** |
|
152 | - * Processes the token outside the scope in the file. |
|
153 | - * |
|
154 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
155 | - * token was found. |
|
156 | - * @param int $stackPtr The position where the token was found. |
|
157 | - * |
|
158 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
159 | - * called again on the current file until the returned stack |
|
160 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
161 | - * the rest of the file. |
|
162 | - */ |
|
163 | - final protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) |
|
164 | - { |
|
165 | - $tokens = $phpcsFile->getTokens(); |
|
166 | - // These variables are not member vars. |
|
167 | - if ($tokens[$stackPtr]['code'] === T_VARIABLE) { |
|
168 | - return $this->processVariable($phpcsFile, $stackPtr); |
|
169 | - } else if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING |
|
170 | - || $tokens[$stackPtr]['code'] === T_HEREDOC |
|
171 | - ) { |
|
172 | - // Check to see if this string has a variable in it. |
|
173 | - $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|'; |
|
174 | - if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) { |
|
175 | - return $this->processVariableInString($phpcsFile, $stackPtr); |
|
176 | - } |
|
177 | - } |
|
178 | - |
|
179 | - }//end processTokenOutsideScope() |
|
180 | - |
|
181 | - |
|
182 | - /** |
|
183 | - * Called to process class member vars. |
|
184 | - * |
|
185 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
186 | - * token was found. |
|
187 | - * @param int $stackPtr The position where the token was found. |
|
188 | - * |
|
189 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
190 | - * called again on the current file until the returned stack |
|
191 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
192 | - * the rest of the file. |
|
193 | - */ |
|
194 | - abstract protected function processMemberVar(File $phpcsFile, $stackPtr); |
|
195 | - |
|
196 | - |
|
197 | - /** |
|
198 | - * Called to process normal member vars. |
|
199 | - * |
|
200 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
201 | - * token was found. |
|
202 | - * @param int $stackPtr The position where the token was found. |
|
203 | - * |
|
204 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
205 | - * called again on the current file until the returned stack |
|
206 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
207 | - * the rest of the file. |
|
208 | - */ |
|
209 | - abstract protected function processVariable(File $phpcsFile, $stackPtr); |
|
210 | - |
|
211 | - |
|
212 | - /** |
|
213 | - * Called to process variables found in double quoted strings or heredocs. |
|
214 | - * |
|
215 | - * Note that there may be more than one variable in the string, which will |
|
216 | - * result only in one call for the string or one call per line for heredocs. |
|
217 | - * |
|
218 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
219 | - * token was found. |
|
220 | - * @param int $stackPtr The position where the double quoted |
|
221 | - * string was found. |
|
222 | - * |
|
223 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
224 | - * called again on the current file until the returned stack |
|
225 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
226 | - * the rest of the file. |
|
227 | - */ |
|
228 | - abstract protected function processVariableInString(File $phpcsFile, $stackPtr); |
|
25 | + /** |
|
26 | + * List of PHP Reserved variables. |
|
27 | + * |
|
28 | + * Used by various naming convention sniffs. |
|
29 | + * |
|
30 | + * @var array |
|
31 | + */ |
|
32 | + protected $phpReservedVars = [ |
|
33 | + '_SERVER' => true, |
|
34 | + '_GET' => true, |
|
35 | + '_POST' => true, |
|
36 | + '_REQUEST' => true, |
|
37 | + '_SESSION' => true, |
|
38 | + '_ENV' => true, |
|
39 | + '_COOKIE' => true, |
|
40 | + '_FILES' => true, |
|
41 | + 'GLOBALS' => true, |
|
42 | + 'http_response_header' => true, |
|
43 | + 'HTTP_RAW_POST_DATA' => true, |
|
44 | + 'php_errormsg' => true, |
|
45 | + ]; |
|
46 | + |
|
47 | + |
|
48 | + /** |
|
49 | + * Constructs an AbstractVariableTest. |
|
50 | + */ |
|
51 | + public function __construct() |
|
52 | + { |
|
53 | + $scopes = Tokens::$ooScopeTokens; |
|
54 | + |
|
55 | + $listen = [ |
|
56 | + T_VARIABLE, |
|
57 | + T_DOUBLE_QUOTED_STRING, |
|
58 | + T_HEREDOC, |
|
59 | + ]; |
|
60 | + |
|
61 | + parent::__construct($scopes, $listen, true); |
|
62 | + |
|
63 | + }//end __construct() |
|
64 | + |
|
65 | + |
|
66 | + /** |
|
67 | + * Processes the token in the specified PHP_CodeSniffer\Files\File. |
|
68 | + * |
|
69 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
70 | + * token was found. |
|
71 | + * @param int $stackPtr The position where the token was found. |
|
72 | + * @param int $currScope The current scope opener token. |
|
73 | + * |
|
74 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
75 | + * called again on the current file until the returned stack |
|
76 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
77 | + * the rest of the file. |
|
78 | + */ |
|
79 | + final protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) |
|
80 | + { |
|
81 | + $tokens = $phpcsFile->getTokens(); |
|
82 | + |
|
83 | + if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING |
|
84 | + || $tokens[$stackPtr]['code'] === T_HEREDOC |
|
85 | + ) { |
|
86 | + // Check to see if this string has a variable in it. |
|
87 | + $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|'; |
|
88 | + if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) { |
|
89 | + return $this->processVariableInString($phpcsFile, $stackPtr); |
|
90 | + } |
|
91 | + |
|
92 | + return; |
|
93 | + } |
|
94 | + |
|
95 | + // If this token is nested inside a function at a deeper |
|
96 | + // level than the current OO scope that was found, it's a normal |
|
97 | + // variable and not a member var. |
|
98 | + $conditions = array_reverse($tokens[$stackPtr]['conditions'], true); |
|
99 | + $inFunction = false; |
|
100 | + foreach ($conditions as $scope => $code) { |
|
101 | + if (isset(Tokens::$ooScopeTokens[$code]) === true) { |
|
102 | + break; |
|
103 | + } |
|
104 | + |
|
105 | + if ($code === T_FUNCTION || $code === T_CLOSURE) { |
|
106 | + $inFunction = true; |
|
107 | + } |
|
108 | + } |
|
109 | + |
|
110 | + if ($scope !== $currScope) { |
|
111 | + // We found a closer scope to this token, so ignore |
|
112 | + // this particular time through the sniff. We will process |
|
113 | + // this token when this closer scope is found to avoid |
|
114 | + // duplicate checks. |
|
115 | + return; |
|
116 | + } |
|
117 | + |
|
118 | + // Just make sure this isn't a variable in a function declaration. |
|
119 | + if ($inFunction === false && isset($tokens[$stackPtr]['nested_parenthesis']) === true) { |
|
120 | + foreach ($tokens[$stackPtr]['nested_parenthesis'] as $opener => $closer) { |
|
121 | + if (isset($tokens[$opener]['parenthesis_owner']) === false) { |
|
122 | + // Check if this is a USE statement for a closure. |
|
123 | + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), null, true); |
|
124 | + if ($tokens[$prev]['code'] === T_USE) { |
|
125 | + $inFunction = true; |
|
126 | + break; |
|
127 | + } |
|
128 | + |
|
129 | + continue; |
|
130 | + } |
|
131 | + |
|
132 | + $owner = $tokens[$opener]['parenthesis_owner']; |
|
133 | + if ($tokens[$owner]['code'] === T_FUNCTION |
|
134 | + || $tokens[$owner]['code'] === T_CLOSURE |
|
135 | + ) { |
|
136 | + $inFunction = true; |
|
137 | + break; |
|
138 | + } |
|
139 | + } |
|
140 | + }//end if |
|
141 | + |
|
142 | + if ($inFunction === true) { |
|
143 | + return $this->processVariable($phpcsFile, $stackPtr); |
|
144 | + } else { |
|
145 | + return $this->processMemberVar($phpcsFile, $stackPtr); |
|
146 | + } |
|
147 | + |
|
148 | + }//end processTokenWithinScope() |
|
149 | + |
|
150 | + |
|
151 | + /** |
|
152 | + * Processes the token outside the scope in the file. |
|
153 | + * |
|
154 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
155 | + * token was found. |
|
156 | + * @param int $stackPtr The position where the token was found. |
|
157 | + * |
|
158 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
159 | + * called again on the current file until the returned stack |
|
160 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
161 | + * the rest of the file. |
|
162 | + */ |
|
163 | + final protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) |
|
164 | + { |
|
165 | + $tokens = $phpcsFile->getTokens(); |
|
166 | + // These variables are not member vars. |
|
167 | + if ($tokens[$stackPtr]['code'] === T_VARIABLE) { |
|
168 | + return $this->processVariable($phpcsFile, $stackPtr); |
|
169 | + } else if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING |
|
170 | + || $tokens[$stackPtr]['code'] === T_HEREDOC |
|
171 | + ) { |
|
172 | + // Check to see if this string has a variable in it. |
|
173 | + $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|'; |
|
174 | + if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) { |
|
175 | + return $this->processVariableInString($phpcsFile, $stackPtr); |
|
176 | + } |
|
177 | + } |
|
178 | + |
|
179 | + }//end processTokenOutsideScope() |
|
180 | + |
|
181 | + |
|
182 | + /** |
|
183 | + * Called to process class member vars. |
|
184 | + * |
|
185 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
186 | + * token was found. |
|
187 | + * @param int $stackPtr The position where the token was found. |
|
188 | + * |
|
189 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
190 | + * called again on the current file until the returned stack |
|
191 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
192 | + * the rest of the file. |
|
193 | + */ |
|
194 | + abstract protected function processMemberVar(File $phpcsFile, $stackPtr); |
|
195 | + |
|
196 | + |
|
197 | + /** |
|
198 | + * Called to process normal member vars. |
|
199 | + * |
|
200 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
201 | + * token was found. |
|
202 | + * @param int $stackPtr The position where the token was found. |
|
203 | + * |
|
204 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
205 | + * called again on the current file until the returned stack |
|
206 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
207 | + * the rest of the file. |
|
208 | + */ |
|
209 | + abstract protected function processVariable(File $phpcsFile, $stackPtr); |
|
210 | + |
|
211 | + |
|
212 | + /** |
|
213 | + * Called to process variables found in double quoted strings or heredocs. |
|
214 | + * |
|
215 | + * Note that there may be more than one variable in the string, which will |
|
216 | + * result only in one call for the string or one call per line for heredocs. |
|
217 | + * |
|
218 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this |
|
219 | + * token was found. |
|
220 | + * @param int $stackPtr The position where the double quoted |
|
221 | + * string was found. |
|
222 | + * |
|
223 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
224 | + * called again on the current file until the returned stack |
|
225 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
226 | + * the rest of the file. |
|
227 | + */ |
|
228 | + abstract protected function processVariableInString(File $phpcsFile, $stackPtr); |
|
229 | 229 | |
230 | 230 | |
231 | 231 | }//end class |