Skip to content

Commit ef3f708

Browse files
committed
SlevomatCodingStandard.Complexity.Cognitive: deprecate 'maxComplexity' and implement 'warningThreshold' and 'errorThreshold'.. (no breaking change)
1 parent 54d5575 commit ef3f708

File tree

5 files changed

+200
-13
lines changed

5 files changed

+200
-13
lines changed

SlevomatCodingStandard/Sniffs/Complexity/CognitiveSniff.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,17 @@ class CognitiveSniff implements Sniff
101101
T_BREAK => T_BREAK,
102102
];
103103

104-
/** @var int */
105-
public $maxComplexity = 5;
104+
/**
105+
* @deprecated
106+
* @var ?int maximum allowed complexity
107+
*/
108+
public $maxComplexity = null;
109+
110+
/** @var int complexity which will raise warning */
111+
public $warningThreshold = 6;
112+
113+
/** @var int complexity which will raise error */
114+
public $errorThreshold = 6;
106115

107116
/** @var int */
108117
private $cognitiveComplexity = 0;
@@ -136,24 +145,34 @@ public function process(File $phpcsFile, $stackPtr): void
136145
return;
137146
}
138147

148+
if ($this->maxComplexity !== null) {
149+
// maxComplexity is deprecated... if set use it
150+
$this->warningThreshold = $this->maxComplexity + 1;
151+
$this->errorThreshold = $this->maxComplexity + 1;
152+
}
153+
139154
$cognitiveComplexity = $this->computeForFunctionFromTokensAndPosition($stackPtr);
140155

141-
if ($cognitiveComplexity <= $this->maxComplexity) {
156+
if ($cognitiveComplexity < $this->warningThreshold) {
142157
return;
143158
}
144159

145160
$name = $phpcsFile->getDeclarationName($stackPtr);
146161

147-
$phpcsFile->addError(
162+
$errorParameters = [
148163
'Cognitive complexity for "%s" is %d but has to be less than or equal to %d.',
149164
$stackPtr,
150165
self::CODE_COMPLEXITY,
151166
[
152167
$name,
153168
$cognitiveComplexity,
154-
$this->maxComplexity,
155-
]
156-
);
169+
$this->warningThreshold - 1,
170+
],
171+
];
172+
173+
$cognitiveComplexity >= $this->errorThreshold
174+
? $phpcsFile->addError(...$errorParameters)
175+
: $phpcsFile->addWarning(...$errorParameters);
157176
}
158177

159178
public function computeForFunctionFromTokensAndPosition(int $position): int

SlevomatCodingStandard/Sniffs/TestCase.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ protected static function assertNoSniffErrorInFile(File $phpcsFile): void
7676
self::assertEmpty($errors, sprintf('No errors expected, but %d errors found.', count($errors)));
7777
}
7878

79+
protected static function assertNoSniffWarningInFile(File $phpcsFile): void
80+
{
81+
$warnings = $phpcsFile->getWarnings();
82+
self::assertEmpty($warnings, sprintf('No warnings expected, but %d warnings found.', count($warnings)));
83+
}
84+
7985
protected static function assertSniffError(File $phpcsFile, int $line, string $code, ?string $message = null): void
8086
{
8187
$errors = $phpcsFile->getErrors();
@@ -101,6 +107,31 @@ protected static function assertSniffError(File $phpcsFile, int $line, string $c
101107
);
102108
}
103109

110+
protected static function assertSniffWarning(File $phpcsFile, int $line, string $code, ?string $message = null): void
111+
{
112+
$errors = $phpcsFile->getWarnings();
113+
self::assertTrue(isset($errors[$line]), sprintf('Expected warning on line %s, but none found.', $line));
114+
115+
$sniffCode = sprintf('%s.%s', self::getSniffName(), $code);
116+
117+
self::assertTrue(
118+
self::hasError($errors[$line], $sniffCode, $message),
119+
sprintf(
120+
'Expected warning %s%s, but none found on line %d.%sWarnings found on line %d:%s%s%s',
121+
$sniffCode,
122+
$message !== null
123+
? sprintf(' with message "%s"', $message)
124+
: '',
125+
$line,
126+
PHP_EOL . PHP_EOL,
127+
$line,
128+
PHP_EOL,
129+
self::getFormattedErrors($errors[$line]),
130+
PHP_EOL
131+
)
132+
);
133+
}
134+
104135
protected static function assertNoSniffError(File $phpcsFile, int $line): void
105136
{
106137
$errors = $phpcsFile->getErrors();

doc/complexity.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ Enforces maximum [cognitive complexity](https://www.sonarsource.com/docs/Cogniti
66

77
Sniff provides the following setting:
88

9-
* `maxComplexity`: defaults to 5
9+
* `warningThreshold`: defaults to 6
10+
* `errorThreshold` : defaults to 6

tests/Sniffs/Complexity/CognitiveSniffTest.php

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,25 +93,135 @@ public function dataProviderFiles(): array
9393
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
9494
* @dataProvider dataProviderFiles
9595
*/
96-
public function testNoErrors(string $filepath, int $line, string $functionName, int $expectedComplexity): void
96+
public function testNoErrorsOrWarnings(string $filepath, int $line, string $functionName, int $expectedComplexity): void
9797
{
9898
$report = self::checkFile($filepath, [
99-
'maxComplexity' => $expectedComplexity,
99+
'errorThreshold' => $expectedComplexity + 1,
100+
'warningThreshold' => $expectedComplexity + 1,
100101
]);
101102
self::assertNoSniffErrorInFile($report);
103+
self::assertNoSniffWarningInFile($report);
104+
}
105+
106+
/**
107+
* @dataProvider dataProviderFiles
108+
*/
109+
public function testWarnings(string $filepath, int $line, string $functionName, int $expectedComplexity): void
110+
{
111+
$report = self::checkFile($filepath, [
112+
'errorThreshold' => $expectedComplexity + 1,
113+
'warningThreshold' => $expectedComplexity,
114+
]);
115+
116+
self::assertSame(1, $report->getWarningCount());
117+
self::assertNoSniffErrorInFile($report);
118+
self::assertSniffWarning(
119+
$report,
120+
$line,
121+
CognitiveSniff::CODE_COMPLEXITY,
122+
sprintf(
123+
'Cognitive complexity for "%s" is %s but has to be less than or equal to %s.',
124+
$functionName,
125+
$expectedComplexity,
126+
$expectedComplexity - 1
127+
)
128+
);
102129
}
103130

104131
/**
105132
* @dataProvider dataProviderFiles
106133
*/
107134
public function testErrors(string $filepath, int $line, string $functionName, int $expectedComplexity): void
108135
{
109-
$maxComplexity = $expectedComplexity - 1;
110136
$report = self::checkFile($filepath, [
111-
'maxComplexity' => $maxComplexity,
137+
'errorThreshold' => $expectedComplexity,
138+
'warningThreshold' => $expectedComplexity - 1,
139+
]);
140+
141+
self::assertSame(1, $report->getErrorCount());
142+
self::assertNoSniffWarningInFile($report);
143+
self::assertSniffError(
144+
$report,
145+
$line,
146+
CognitiveSniff::CODE_COMPLEXITY,
147+
sprintf(
148+
'Cognitive complexity for "%s" is %s but has to be less than or equal to %s.',
149+
$functionName,
150+
$expectedComplexity,
151+
$expectedComplexity - 2
152+
)
153+
);
154+
}
155+
156+
public function testErrorAndWarning(): void
157+
{
158+
$filepath = __DIR__ . '/data/cognitive/warnAndError.php';
159+
$warnInfo = [
160+
'complexity' => 6,
161+
'func' => 'warning',
162+
'line' => 3,
163+
];
164+
$errorInfo = [
165+
'complexity' => 9,
166+
'func' => 'error',
167+
'line' => 15,
168+
];
169+
170+
$report = self::checkFile($filepath, [
171+
'errorThreshold' => 9,
172+
'warningThreshold' => 6,
173+
]);
174+
175+
self::assertSame(1, $report->getWarningCount());
176+
self::assertSame(1, $report->getErrorCount());
177+
self::assertSniffWarning(
178+
$report,
179+
$warnInfo['line'],
180+
CognitiveSniff::CODE_COMPLEXITY,
181+
sprintf(
182+
'Cognitive complexity for "%s" is %s but has to be less than or equal to %s.',
183+
$warnInfo['func'],
184+
$warnInfo['complexity'],
185+
5
186+
)
187+
);
188+
self::assertSniffError(
189+
$report,
190+
$errorInfo['line'],
191+
CognitiveSniff::CODE_COMPLEXITY,
192+
sprintf(
193+
'Cognitive complexity for "%s" is %s but has to be less than or equal to %s.',
194+
$errorInfo['func'],
195+
$errorInfo['complexity'],
196+
5
197+
)
198+
);
199+
}
200+
201+
/**
202+
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
203+
* @dataProvider dataProviderFiles
204+
*/
205+
public function testDeprecatedNoErrorsOrWarnings(string $filepath, int $line, string $functionName, int $expectedComplexity): void
206+
{
207+
$report = self::checkFile($filepath, [
208+
'maxComplexity' => $expectedComplexity,
209+
]);
210+
self::assertNoSniffErrorInFile($report);
211+
self::assertNoSniffWarningInFile($report);
212+
}
213+
214+
/**
215+
* @dataProvider dataProviderFiles
216+
*/
217+
public function testDeprecatedErrors(string $filepath, int $line, string $functionName, int $expectedComplexity): void
218+
{
219+
$report = self::checkFile($filepath, [
220+
'maxComplexity' => $expectedComplexity - 1,
112221
]);
113222

114223
self::assertSame(1, $report->getErrorCount());
224+
self::assertNoSniffWarningInFile($report);
115225
self::assertSniffError(
116226
$report,
117227
$line,
@@ -120,7 +230,7 @@ public function testErrors(string $filepath, int $line, string $functionName, in
120230
'Cognitive complexity for "%s" is %s but has to be less than or equal to %s.',
121231
$functionName,
122232
$expectedComplexity,
123-
$maxComplexity
233+
$expectedComplexity - 1
124234
)
125235
);
126236
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
function warning($var)
4+
{
5+
try {
6+
if (true) { // +1
7+
for ($i = 0; $i < 10; $i++) { // +2 (nesting=1)
8+
}
9+
}
10+
} catch (\Exception | \Exception $exception) { // +1
11+
if (true) { } // +2 (nesting=1)
12+
}
13+
}
14+
15+
function error($var)
16+
{
17+
try {
18+
if (true) { // +1
19+
for ($i = 0; $i < 10; $i++) { // +2 (nesting=1)
20+
while (true) { } // +3 (nesting=2)
21+
}
22+
}
23+
} catch (\Exception | \Exception $exception) { // +1
24+
if (true) { } // +2 (nesting=1)
25+
}
26+
}

0 commit comments

Comments
 (0)