Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Universal.DeclareStatements.DeclareStatementsStyle sniff #129

Open
wants to merge 126 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
a70635c
Add new sniff that checks the declare statements
dingo-d Aug 27, 2022
7e27d1b
Remove renamed files
dingo-d Sep 25, 2022
98925a9
Rewrite the sniff according to the PR suggestions
dingo-d Sep 25, 2022
74ee334
Update docs for the sniff
dingo-d Sep 25, 2022
0acf7a2
Update test file
dingo-d Sep 25, 2022
bfcf2b4
Split code examples for the test in multiple files
dingo-d Sep 25, 2022
dede6f8
Initial attempt at adding metrics
dingo-d Dec 3, 2022
a8d53eb
Remove fixer comments
dingo-d Dec 3, 2022
5834c64
WIP / review - TODO: rest of sniff + tests reviewen
jrfnl Dec 5, 2022
9a6284e
:sparkles: New `Universal.CodeAnalysis.StaticInFinalClass` sniff
jrfnl Apr 24, 2022
e573d36
:sparkles: New `Universal.Operators.TypeSeparatorSpacing` sniff
jrfnl May 6, 2022
c1f8436
NormalizedArrays/ArrayBraceSpacing: allow for trailing comments after…
jrfnl Jul 1, 2022
772e7d8
:sparkles: New `Universal.WhiteSpace.PrecisionAlignment` sniff
jrfnl Jul 20, 2022
1e78d8e
:sparkles: New `Universal.WhiteSpace.AnonClassKeywordSpacing` sniff
jrfnl Jul 22, 2022
dacf3d8
QA: make all classes `final`
jrfnl Mar 29, 2022
6a3690c
Universal/PrecisionAlignment: rename a local variable
jrfnl Jul 29, 2022
053fa6a
Universal/PrecisionAlignment: remove superfluous fixed file
jrfnl Jul 29, 2022
d6ce6ef
WhiteSpace/PrecisionAlignment: best guess tabs vs spaces when fixing
jrfnl Jul 29, 2022
58fe9e5
WhiteSpace/PrecisionAlignment: bug fix - improved handling of heredoc…
jrfnl Jul 29, 2022
40140a7
Add "static analysis" Composer keyword
GaryJones Sep 4, 2022
72e5868
Composer/GH Actions: start using PHPCSDevTools 1.2.0
jrfnl Jun 8, 2022
7cd20f1
Sniff XML docs: add schema to docs
jrfnl Jun 8, 2022
3e9f284
Composer: up the minimum PHPCS version to 3.7.1
jrfnl Oct 13, 2022
ac0b761
Changelog: improve maintainability and source readability
jrfnl Oct 13, 2022
da74f92
GH Actions: fix use of deprecated `set-output`
jrfnl Oct 14, 2022
d901f9b
GH Actions: update the xmllint-problem-matcher
jrfnl Oct 14, 2022
9fb1b07
GH Actions/basics: revert to xmllint-problem-matcher v1
jrfnl Oct 23, 2022
ed39208
GH Actions: harden the workflow against PHPCS ruleset errors
jrfnl Oct 23, 2022
b1620ea
Upgrade to PHPCSUtils 1.0.0-alpha4
jrfnl Oct 13, 2022
e20b0a6
:sparkles: New `Universal.Files.SeparateFunctionsFromOO` sniff
jrfnl Jul 25, 2021
a7c61fc
NormalizedArrays/ArrayBraceSpacing: safeguard upstream bugfix
jrfnl Nov 8, 2020
ec8166e
DisallowStandalonePostIncrementDecrement: prevent looking for nullsaf…
jrfnl Aug 2, 2020
542d969
:sparkles: New `Universal.CodeAnalysis.ConstructorDestructorReturn` s…
jrfnl Oct 26, 2022
8b5d591
QA: remove some redundant/unused code
jrfnl Jun 13, 2022
28326cd
CS: minor fixes
jrfnl Jul 1, 2022
39bc8e4
Documentation: various minor fixes
jrfnl Oct 26, 2022
c81abd2
DisallowInlineTabsUnitTest: skip on PHP 5.5
jrfnl Oct 26, 2022
d0dfd66
Universal/ConstructorDestructorReturn: add auto-fixer for return type
jrfnl Oct 26, 2022
00d0150
:sparkles: New `Universal.Classes.ModifierKeywordOrder` sniff
jrfnl Oct 29, 2022
50383a9
:sparkles: New `Universal.Constants.ModifierKeywordOrder` sniff
jrfnl Oct 30, 2022
7bdb46b
NormalizedArrays/CommaAfterLast: improve fixer for flexible heredoc/n…
jrfnl Oct 27, 2022
ee9225d
QA: always declare metric names as class constants
jrfnl Oct 31, 2022
334c8dc
Universal/ConstructorDestructorReturn: improve return type fixer
jrfnl Nov 2, 2022
bffb7c3
Universal/OneStatementInShortEchoTag: improve error code
jrfnl Nov 2, 2022
253c637
Universal/[Require/Disallow]FinalClass: rename metric
jrfnl Oct 31, 2022
545f531
Universal/[Require/Disallow]FinalClass: add tests with readonly classes
jrfnl Nov 2, 2022
f14adb5
Universal/DisallowUseClass: "docs" update for enums
jrfnl Nov 2, 2022
58ec58a
Universal/AlphabeticExtendsImplements: docs/test update for enums
jrfnl Nov 2, 2022
ed4cfc6
Universal/DisallowShortListSyntax: allow for tokenizer issue in PHPCS…
jrfnl Oct 27, 2022
685996f
Universal/DisallowShortListSyntax: bug fix - don't skip over nested b…
jrfnl Oct 28, 2022
33f2e6e
Universal/DisallowShortArraySyntax: don't skip over short lists
jrfnl Oct 28, 2022
f1646d9
Universal/DisallowShortArraySyntax: record metrics
jrfnl Oct 27, 2022
2d06343
Universal/DisallowLongListSyntax: don't record metrics
jrfnl Oct 27, 2022
731f000
Universal/DisallowShortListSyntax: improve metric recording
jrfnl Oct 27, 2022
d3b8a6e
GH Actions: bust the cache semi-regularly
jrfnl Nov 4, 2022
011eaae
Universal/DisallowAlternativeSyntax: improve test code
jrfnl Nov 12, 2022
8288e14
Universal/DisallowAlternativeSyntax: add additional tests with empty …
jrfnl Nov 12, 2022
8af66a6
Universal/DisallowAlternativeSyntax: minor docs improvements
jrfnl Nov 12, 2022
0100cbe
Universal/DisallowAlternativeSyntax: improve metrics
jrfnl Nov 13, 2022
fb697d3
Universal/DisallowAlternativeSyntax: improve the error message [1]
jrfnl Nov 6, 2022
a161fc2
Universal/DisallowAlternativeSyntax: improve the error message [2]
jrfnl Nov 13, 2022
954294a
Universal/DisallowAlternativeSyntax: minor tweak
jrfnl Nov 14, 2022
2c30b2c
Universal/DisallowAlternativeSyntax: bug fix - ignore inline HTML in …
jrfnl Nov 13, 2022
dc6c987
Universal/DisallowAlternativeSyntax: bug fix - handle if/elseif state…
jrfnl Nov 14, 2022
8c187c0
Universal/DisallowAnonClassParentheses: remove redundant condition
jrfnl Dec 1, 2022
3eaadf4
Universal/RequireAnonClassParentheses: remove redundant condition
jrfnl Dec 1, 2022
d718bae
Universal/DisallowFinalClass: minor code tweak
jrfnl Nov 30, 2022
615b28b
Universal/DisallowFinalClass: make tests more descriptive
jrfnl Nov 30, 2022
c303f58
Universal/DisallowFinalClass: always record the metric
jrfnl Dec 1, 2022
407f592
Universal/DisallowFinalClass: act on more cases
jrfnl Nov 30, 2022
f2e465b
Universal/RequireFinalClass: make tests more descriptive
jrfnl Nov 30, 2022
97d836c
Universal/RequireFinalClass: always record the metric
jrfnl Dec 1, 2022
7635da1
Universal/RequireFinalClass: act on more cases
jrfnl Nov 30, 2022
d9f0cf1
Universal/OneStatementInShortEchoTag: prevent false positive
jrfnl Nov 30, 2022
50b4bde
Universal/NoLeadingBackslash: minor code reorganization
jrfnl Dec 1, 2022
56cf767
Universal/NoLeadingBackslash: examine imports within a group use stat…
jrfnl Dec 1, 2022
4dca87c
Universal/DisallowLonelyIf: add extra test
jrfnl Dec 1, 2022
6044232
Universal/DisallowLonelyIf: bow out early for a specific situation (P…
jrfnl Dec 1, 2022
a4c3e45
Universal/SeparateFunctionsFromOO: update tests for enums
jrfnl Dec 1, 2022
a73b66b
Universal/SeparateFunctionsFromOO: update tests for arrow functions
jrfnl Dec 1, 2022
9e785bf
Introduce new `Modernize` standard
jrfnl Nov 29, 2022
4473589
:sparkles: New `Modernize.FunctionCalls.Dirname` sniff
jrfnl Nov 29, 2022
63d67fe
Universal/NoReservedKeywordParameterNames: minor code simplification
jrfnl Dec 3, 2022
df7e718
Universal/NoReservedKeywordParameterNames: make tests more descriptive
jrfnl Dec 3, 2022
d9e60ce
Universal/NoReservedKeywordParameterNames: add extra tests
jrfnl Dec 3, 2022
9dd2e18
Universal/NoReservedKeywordParameterNames: make the check case-insens…
jrfnl Dec 3, 2022
0627b70
Universal/NoReservedKeywordParameterNames: add tests with PHP 8.0 con…
jrfnl Dec 3, 2022
765e4b3
Universal/DisallowUse[Class|Const|Function]: minor code coverage tweak
jrfnl Dec 3, 2022
bd76b7c
Universal/ForeachUniqueAssignment: fix some tests
jrfnl Dec 4, 2022
1559d36
Universal/ForeachUniqueAssignment: improve analysis for foreach list …
jrfnl Dec 4, 2022
c9143c0
Universal/DisallowStandalonePostIncrementDecrement: allow for stateme…
jrfnl Dec 4, 2022
3342beb
Universal/DuplicateArrayKey: add support for detecting duplicate key …
jrfnl May 4, 2020
012f9fe
Universal/DuplicateArrayKey: code simplification
jrfnl Dec 6, 2022
d830ed3
CS/QA: various minor code tweaks
jrfnl Oct 27, 2022
4bd1a76
Docs: normalize `@since` tags
jrfnl Oct 31, 2022
c44b775
DisallowAnonClassParentheses: minor code readability improvement
jrfnl Nov 2, 2022
7d2f692
Universal/PrecisionAlignment: update comment
jrfnl Nov 2, 2022
082a661
Docs: improve a few inline comments
jrfnl Dec 1, 2022
f6595e4
Universal/StaticInFinalClass: handle `static` when used in arrow func…
jrfnl Dec 7, 2022
45f09c9
Composer: add PHPCSDevCS to the dependencies
jrfnl Dec 7, 2022
c1081b3
GH Actions/test: up the minimum required version of the coverall tooling
jrfnl Dec 7, 2022
bb3597a
GH Actions/test: remove unused steps related to PHPCS 4.x
jrfnl Dec 7, 2022
6d828cb
GH Actions: no longer allow builds to fail against PHP 8.2
jrfnl Dec 7, 2022
2584ccf
Changelog and readme updates for release `1.0.0-RC1`
jrfnl Oct 13, 2022
25efbd5
README: update badges
jrfnl Apr 24, 2022
4f603c9
README: add updating instructions
jrfnl Oct 13, 2022
6f8e8c2
README: misc updates
jrfnl Dec 7, 2022
c6c4cfe
Changelog: add missing link
jrfnl Dec 7, 2022
b38a526
GH Actions/basics: move composer validate up
jrfnl Dec 7, 2022
88a5985
GH Actions: add new check with additional QA for markdown files
jrfnl Dec 7, 2022
bb8c884
GH Actions: add Yamllint to QA basics
jrfnl Dec 7, 2022
86293d0
Modernize/Dirname: magic constants are case-insensitive
jrfnl Dec 8, 2022
12ef67f
GH Actions: minor simplification
jrfnl Dec 8, 2022
050ae07
GH Actions: update PHP versions in workflows
jrfnl Dec 8, 2022
d6ca9a0
.gitattributes: update export-ignores
jrfnl Dec 9, 2022
9dc1bc5
Add new sniff that checks the declare statements
dingo-d Aug 27, 2022
1f8e6bb
Remove renamed files
dingo-d Sep 25, 2022
b89ae7e
Rewrite the sniff according to the PR suggestions
dingo-d Sep 25, 2022
a689164
Update docs for the sniff
dingo-d Sep 25, 2022
1ddfaa6
Update test file
dingo-d Sep 25, 2022
fc79476
Split code examples for the test in multiple files
dingo-d Sep 25, 2022
71b2116
Initial attempt at adding metrics
dingo-d Dec 3, 2022
8562dce
Remove fixer comments
dingo-d Dec 3, 2022
fd64ff1
WIP / review - TODO: rest of sniff + tests reviewen
jrfnl Dec 5, 2022
b5e85cb
Merge remote-tracking branch 'origin/declare-statements-curly-bracket…
dingo-d May 28, 2023
2d759b3
Replace tabs with spaces
dingo-d May 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions Universal/Docs/DeclareStatements/BlockModeStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0"?>
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd"
title="Declare Statements Block Mode"
>
<standard>
<![CDATA[
Defines the block mode usage of declare directives.
]]>
</standard>
<code_comparison>
<code title="Valid: Declare statement written in non-block mode.">
<![CDATA[
declare(strict_types=1);

declare(encoding='utf-8');

declare(ticks=10):
// Code.
enddeclare;
]]>
</code>
<code title="Invalid: Declare statement written with curly braces or alternative syntax.">
<![CDATA[
declare(encoding='ISO-8859-1', ticks=1) {
// Code.
}

declare(encoding='ISO-8859-1', ticks=10):
declare(encoding='utf-8'):
// Code.
enddeclare;
enddeclare;
]]>
</code>
</code_comparison>
<standard>
<![CDATA[
A declare statement for the `strict_types` directive is not allowed to be written using curly braces or with alternative syntax.
]]>
</standard>
<code_comparison>
<code title="Valid: strict_types statement not using block mode.">
<![CDATA[
declare(strict_types=1);
]]>
</code>
<code title="Invalid: strict_types statement using alternative syntax/curly braces.">
<![CDATA[
declare(strict_types=1) <em>{</em>
// Code.
<em>}</em>

declare(strict_types=1) <em>:</em>
// Code.
<em>enddeclare</em>;
]]>
</code>
</code_comparison>
</documentation>
287 changes: 287 additions & 0 deletions Universal/Sniffs/DeclareStatements/BlockModeSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/

namespace PHPCSExtra\Universal\Sniffs\DeclareStatements;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\TextStrings;

/**
* Checks the style of the declare statement.
*
* Declare statements can be written in two different styles:
* 1. Applied to the rest of the file, usually written at the top of a file like `declare(strict_types=1);`.
* 2. Applied to a limited scope using curly braces or using alternative control structure syntax
* (the exception to this rule is the `strict_types` directive). This is known as block mode.
*
* There can be multiple directives inside a `declare` statement.
* This sniff checks the preferred mode for the `declare` statements.
*
* You can modify the sniff by changing the whether the block mode of the encoding and the ticks directives
* is allowed, disallowed or required. By default, the ticks directive, if written in
* the block mode won't trigger an error, while encoding and strict_types directives will.
*
* strict_types directive is the only one that cannot be modified because it can only be used in
* a non-block mode.
*
* @since 1.0.0
*/
class BlockModeSniff implements Sniff
{

/**
* Name of the metric.
*
* @since 1.0.0
*
* @var string
*/
const DECLARE_SCOPE_METRIC = 'Declare statement scope';

/**
* Name of the metric.
*
* @since 1.0.0
*
* @var string
*/
const DECLARE_TYPE_METRIC = 'Declare directive type';

/**
* Whether block mode is allowed for `encoding` directives.
*
* Can be one of: 'disallow', 'allow' (no preference), or 'require'.
* Defaults to: 'disallow'.
*
* @since 1.0.0
*
* @var string
*/
public $encodingBlockMode = 'disallow';

/**
* Whether block mode is allowed for `ticks` directives.
*
* Can be one of: 'disallow', 'allow' (no preference), or 'require'.
* Defaults to: 'allow'.
*
* @since 1.0.0
*
* @var string
*/
public $ticksBlockMode = 'allow';

/**
* The default option for the strict_types directive.
*
* Block mode is not allowed for the `strict_types` directive.
* Using it in block mode will throw a PHP fatal error.
*
* @since 1.0.0
*
* @var string
*/
private $strictTypesBlockMode = 'disallow';

/**
* Allowed declare directives.
*
* @since 1.0.0
*
* @var array
*/
private $allowedDirectives = [
'strict_types' => true,
'ticks' => true,
'encoding' => true,
];

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 1.0.0
*
* @return array
*/
public function register()
{
return [T_DECLARE];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
// Parse error or live coding, bow out.
return;
}

$openParenPtr = $tokens[$stackPtr]['parenthesis_opener'];
$closeParenPtr = $tokens[$stackPtr]['parenthesis_closer'];

$directiveStrings = [];
// Get the next string and check if it's an allowed directive.
// Find all the directive strings inside the declare statement.
for ($i = $openParenPtr; $i <= $closeParenPtr; $i++) {
if ($tokens[$i]['code'] === \T_STRING) {
$contentsLC = \strtolower($tokens[$i]['content']);
if (isset($this->allowedDirectives[$contentsLC])) {
$phpcsFile->recordMetric($i, self::DECLARE_TYPE_METRIC, $contentsLC);
$directiveStrings[$contentsLC] = true;
}
}
}

unset($i);

if (empty($directiveStrings)) {
// No valid directives were found, this is outside the scope of this sniff.
return;
}

$usesBlockMode = isset($tokens[$stackPtr]['scope_opener']);

if ($usesBlockMode) {
$phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'Block mode');
} else {
$phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'File mode');
}

// If strict types is defined using block mode, throw error.
if ($usesBlockMode && isset($directiveStrings['strict_types'])) {
$error = 'strict_types declaration must not use block mode.';
$code = 'Forbidden';

if (isset($tokens[$stackPtr]['scope_closer'])) {
// If there is no scope closer, we cannot auto-fix.
$phpcsFile->addError($error, $stackPtr, $code);
return;
}

$fix = $phpcsFile->addFixableError($error, $stackPtr, $code);

if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent($closeParenPtr, ';');
$phpcsFile->fixer->replaceToken($tokens[$stackPtr]['scope_opener'], '');

// Remove potential whitespace between parenthesis closer and the brace.
for ($i = ($tokens[$stackPtr]['scope_opener'] - 1); $i > 0; $i--) {
if ($tokens[$i]['code'] !== \T_WHITESPACE) {
break;
}

$phpcsFile->fixer->replaceToken($i, '');
}

$phpcsFile->fixer->replaceToken($tokens[$stackPtr]['scope_closer'], '');
$phpcsFile->fixer->endChangeset();
}
return;
}

// Check if there is code between the declare statement and opening brace/alternative syntax.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParenPtr + 1), null, true);
if ($tokens[$nextNonEmpty]['code'] !== \T_SEMICOLON
&& $tokens[$nextNonEmpty]['code'] !== \T_CLOSE_TAG
&& $tokens[$nextNonEmpty]['code'] !== \T_OPEN_CURLY_BRACKET
&& $tokens[$nextNonEmpty]['code'] !== \T_COLON
) {
$phpcsFile->addError(
'Unexpected code found after the declare statement.',
$stackPtr,
'UnexpectedCodeFound'
);
return;
}

// Multiple directives - if one requires block mode usage, other has to as well.
if (count($directiveStrings) > 1
&& (($this->encodingBlockMode === 'disallow' && $this->ticksBlockMode !== 'disallow')
|| ($this->ticksBlockMode === 'disallow' && $this->encodingBlockMode !== 'disallow'))
) {
$phpcsFile->addError(
'Multiple directives found, but one of them is disallowing the use of block mode.',
$stackPtr,
'Forbidden' // <= Duplicate error code for different message (line 175)
);
return;
}

if (($this->encodingBlockMode === 'allow' || $this->encodingBlockMode === 'require')
&& $this->ticksBlockMode === 'disallow'
&& $usesBlockMode && isset($directiveStrings['ticks'])
) {
$phpcsFile->addError(
'Block mode is not allowed for ticks directive.',
$stackPtr,
'DisallowedTicksBlockMode'
);
return;
}

if ($this->ticksBlockMode === 'require'
&& !$usesBlockMode && isset($directiveStrings['ticks'])
) {
$phpcsFile->addError(
'Block mode is required for ticks directive.',
$stackPtr,
'RequiredTicksBlockMode'
);
return;
}

if ($this->encodingBlockMode === 'disallow'
&& ($this->ticksBlockMode === 'allow' || $this->ticksBlockMode === 'require')
&& $usesBlockMode && isset($directiveStrings['encoding'])
) {
$phpcsFile->addError(
'Block mode is not allowed for encoding directive.',
$stackPtr,
'DisallowedEncodingBlockMode'
);
return;
}

if ($this->encodingBlockMode === 'disallow' && $this->ticksBlockMode === 'disallow' && $usesBlockMode) {
$phpcsFile->addError(
'Block mode is not allowed for any declare directive.',
$stackPtr,
'DisallowedBlockMode'
);
return;
}

if ($this->encodingBlockMode === 'require'
&& !$usesBlockMode && isset($directiveStrings['encoding'])
) {
$phpcsFile->addError(
'Block mode is required for encoding directive.',
$stackPtr,
'RequiredEncodingBlockMode'
);
return;
}
}
}
Loading