Skip to content

Commit 6511b3e

Browse files
committed
Add new sniff that checks the declare statements
1 parent 7793726 commit 6511b3e

File tree

4 files changed

+850
-0
lines changed

4 files changed

+850
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0"?>
2+
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd"
4+
title="Declare Statements Style"
5+
>
6+
<standard>
7+
<![CDATA[
8+
Declare statements should, by default, be written without curly braces/alternative syntax.
9+
]]>
10+
</standard>
11+
<code_comparison>
12+
<code title="Valid: Declare statement written without curly braces.">
13+
<![CDATA[
14+
declare(strict_types=1);
15+
]]>
16+
</code>
17+
<code title="Invalid: Declare statement written with curly braces or alternative syntax.">
18+
<![CDATA[
19+
declare(encoding='ISO-8859-1', ticks=1) {
20+
// Code.
21+
}
22+
23+
declare(encoding='ISO-8859-1', ticks=10):
24+
declare(ticks=1):
25+
// Code.
26+
enddeclare;
27+
enddeclare;
28+
]]>
29+
</code>
30+
</code_comparison>
31+
<standard>
32+
<![CDATA[
33+
strict_types declaration directive mustn't be written using curly braces, or with alternative syntax.
34+
]]>
35+
</standard>
36+
<code_comparison>
37+
<code title="Valid: strict_types written without curly braces.">
38+
<![CDATA[
39+
declare(strict_types=1);
40+
]]>
41+
</code>
42+
<code title="Invalid: strict_types written using alternative syntax/curly braces.">
43+
<![CDATA[
44+
declare(strict_types=1, ticks=1) {
45+
// Code.
46+
}
47+
]]>
48+
</code>
49+
</code_comparison>
50+
</documentation>
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<?php
2+
/**
3+
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4+
*
5+
* @package PHPCSExtra
6+
* @copyright 2020 PHPCSExtra Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSExtra
9+
*/
10+
11+
namespace PHPCSExtra\Universal\Sniffs\DeclareStatements;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Sniffs\Sniff;
15+
use PHP_CodeSniffer\Util\Tokens;
16+
use PHPCSUtils\Utils\TextStrings;
17+
18+
/**
19+
* Checks the style of the declare statement
20+
*
21+
* `declare` directive can be written in different styles:
22+
* 1. Applied to the rest of the file, usually written at the top of a file like `declare(strict_types=1);`
23+
* 2. Applied to a limited scope using curly braces (the exception to this rule is the `strict_types` directive)
24+
* 3. Applied to a limited scope using alternative control structure syntax
25+
*
26+
* You can also have multiple directives written inside the `declare` directive.
27+
* This sniff will check the style of the `declare` directive - by default applied to all directives,
28+
* written without braces.
29+
*
30+
* You can modify the sniff by changing the `declareStyle` option to require or disallow braces,
31+
* and require or allow the style to only one of the two directives (`ticks` and `encoding`).
32+
*
33+
* @since 1.0.0
34+
*/
35+
class DeclareStatementsStyleSniff implements Sniff
36+
{
37+
38+
/**
39+
* Whether to require curly brace style or disallow it
40+
* when using declare statements.
41+
*
42+
* Possible values: requireBraces, disallowBraces.
43+
*
44+
* Default is disallowBraces.
45+
*
46+
* @var string
47+
*/
48+
public $declareStyle = 'disallowBraces';
49+
50+
/**
51+
* Which declare to enforce the brace style for.
52+
* If not explicitly defined, the brace style will be applied
53+
* to all declare directive.
54+
*
55+
* The exception is the strict_types directive which cannot be
56+
* written using curly braces.
57+
*
58+
* Possible values: ticks, encoding.
59+
*
60+
* @var array
61+
*/
62+
public $directiveType = [];
63+
64+
/**
65+
* Allowed declare directives.
66+
*
67+
* @var array
68+
*/
69+
private $allowedDirectives = [
70+
'strict_types',
71+
'ticks',
72+
'encoding',
73+
];
74+
75+
/**
76+
* Returns an array of tokens this test wants to listen for.
77+
*
78+
* @since 1.0.0
79+
*
80+
* @return array
81+
*/
82+
public function register()
83+
{
84+
return [
85+
T_DECLARE
86+
];
87+
}
88+
89+
/**
90+
* Processes this test, when one of its tokens is encountered.
91+
*
92+
* @since 1.0.0
93+
*
94+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
95+
* @param int $stackPtr The position of the current token
96+
* in the stack passed in $tokens.
97+
*
98+
* @return void
99+
*/
100+
public function process(File $phpcsFile, $stackPtr)
101+
{
102+
$tokens = $phpcsFile->getTokens();
103+
104+
$openParenPtr = $tokens[$stackPtr]['parenthesis_opener'];
105+
$closeParenPtr = $tokens[$stackPtr]['parenthesis_closer'];
106+
107+
if (isset($openParenPtr, $closeParenPtr) === false) {
108+
// Parse error or live coding, bow out.
109+
return;
110+
}
111+
112+
$directiveStrings = [];
113+
// Get the next string and check if it's an allowed directive.
114+
// Find all the directive strings inside the declare statement.
115+
for ($i = $openParenPtr; $i <= $closeParenPtr; $i++) {
116+
if ($tokens[$i]['code'] === \T_STRING) {
117+
$directiveStrings[] = $tokens[$i]['content'];
118+
}
119+
}
120+
121+
foreach ($directiveStrings as $directiveString) {
122+
if (!in_array($directiveString, $this->allowedDirectives, true)) {
123+
$phpcsFile->addError(
124+
sprintf(
125+
'Declare directives can be one of: %1$s. "%2$s" found.',
126+
implode(', ', $this->allowedDirectives),
127+
$directiveString
128+
),
129+
$stackPtr,
130+
'WrongDeclareDirective'
131+
);
132+
return;
133+
}
134+
unset($directiveString);
135+
}
136+
137+
// Curly braces.
138+
$hasScopeOpenerCloser = isset($tokens[$stackPtr]['scope_opener']);
139+
140+
// If strict types is defined using curly brace, throw error.
141+
if ($hasScopeOpenerCloser !== false && in_array('strict_types', $directiveStrings, true)) {
142+
$phpcsFile->addError(
143+
sprintf(
144+
'strict_types declaration must not use block mode. Opening brace found on line %d',
145+
$tokens[$stackPtr]['line']
146+
),
147+
$stackPtr,
148+
'Forbidden'
149+
);
150+
return;
151+
}
152+
153+
// Fix for the case when the code is between the curly braces for the strict_types.
154+
$codePtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParenPtr + 1), null, true);
155+
156+
/* If the code pointer is not one of:
157+
* \T_SEMICOLON, \T_CLOSE_TAG or \T_OPEN_CURLY_BRACKET, \T_COLON
158+
* throw an error.
159+
*/
160+
if (!in_array($tokens[$codePtr]['code'], [\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_CURLY_BRACKET, \T_COLON], true)) {
161+
$phpcsFile->addError(
162+
'Unexpected code found after opening the declare statement without closing it.',
163+
$stackPtr,
164+
'UnexpectedCodeFound'
165+
);
166+
return;
167+
}
168+
169+
if ($this->declareStyle === 'requireBraces' &&
170+
$hasScopeOpenerCloser === false &&
171+
in_array('strict_types', $directiveStrings, true)
172+
) {
173+
$phpcsFile->addError(
174+
'strict_types declaration is not compatible with requireBraces option.',
175+
$stackPtr,
176+
'IncompatibleCurlyBracesRequirement'
177+
);
178+
return;
179+
}
180+
181+
if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) {
182+
$phpcsFile->addError('Declare statement found using curly braces', $stackPtr, 'DisallowedCurlyBraces');
183+
return;
184+
}
185+
186+
if (in_array('ticks', $this->directiveType, true)) {
187+
if ($this->declareStyle === 'requireBraces' &&
188+
$hasScopeOpenerCloser === false &&
189+
in_array('ticks', $directiveStrings, true)
190+
) {
191+
$phpcsFile->addError(
192+
'Declare statement for found without curly braces',
193+
$stackPtr,
194+
'MissingCurlyBracesTicks'
195+
);
196+
return;
197+
}
198+
199+
if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) {
200+
$phpcsFile->addError(
201+
'Declare statement found using curly braces',
202+
$stackPtr,
203+
'DisallowedCurlyBracesTicks'
204+
);
205+
return;
206+
}
207+
}
208+
209+
if (in_array('encoding', $this->directiveType, true)) {
210+
if ($this->declareStyle === 'requireBraces' &&
211+
$hasScopeOpenerCloser === false &&
212+
in_array('encoding', $directiveStrings, true)
213+
) {
214+
$phpcsFile->addError(
215+
'Declare statement found without curly braces',
216+
$stackPtr,
217+
'MissingCurlyBracesEncoding'
218+
);
219+
return;
220+
}
221+
222+
if ($this->declareStyle === 'disallowBraces' && $hasScopeOpenerCloser !== false) {
223+
$phpcsFile->addError(
224+
'Declare statement found using curly braces',
225+
$stackPtr,
226+
'DisallowedCurlyBracesEncoding'
227+
);
228+
return;
229+
}
230+
}
231+
232+
if ($this->declareStyle === 'requireBraces' &&
233+
$hasScopeOpenerCloser === false &&
234+
empty($this->directiveType) &&
235+
!in_array('strict_types', $directiveStrings, true)
236+
) {
237+
$phpcsFile->addError('Declare statement found without curly braces', $stackPtr, 'MissingCurlyBraces');
238+
return;
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)