Skip to content

Commit

Permalink
feat: match constant names pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
simPod committed Sep 20, 2022
1 parent 61a1974 commit 5d22d74
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 52 deletions.
142 changes: 142 additions & 0 deletions src/Cdn77/Sniffs/NamingConventions/ValidConstantNameSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

declare(strict_types=1);

namespace Cdn77\Sniffs\NamingConventions;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;

use function preg_match;
use function sprintf;
use function strpos;
use function strrpos;
use function strtolower;
use function substr;

use const T_CONST;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DOUBLE_COLON;
use const T_NULLSAFE_OBJECT_OPERATOR;
use const T_OBJECT_OPERATOR;
use const T_STRING;
use const T_WHITESPACE;

class ValidConstantNameSniff implements Sniff
{
public const CodeConstantNotMatchPattern = 'ConstantNotUpperCase';
public const CodeClassConstantNotMatchPattern = 'ClassConstantNotUpperCase';
private const PatternPascalCase = '\b([A-Z][a-zA-Z0-9]*?([A-Z][a-zA-Z0-9]*?)*?)\b';

public string $pattern = self::PatternPascalCase;

/**
* Returns an array of tokens this test wants to listen for.
*
* @return list<int>
*/
public function register(): array
{
return [
T_STRING,
T_CONST,
];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the stack passed in $tokens.
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function process(File $phpcsFile, $stackPtr): void
{
$tokens = $phpcsFile->getTokens();

if ($tokens[$stackPtr]['code'] === T_CONST) {
// This is a class constant.
$constant = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
if ($constant === false) {
return;
}

$constName = $tokens[$constant]['content'];

if ($this->matchesRegex($constName, $this->pattern)) {
return;
}

$error = sprintf('Constant "%%s" does not match pattern "%s"', $this->pattern);
$data = [$constName];
$phpcsFile->addError(
$error,
$constant,
self::CodeClassConstantNotMatchPattern,
$data,
);
}

// Only interested in define statements now.
if (strtolower($tokens[$stackPtr]['content']) !== 'define') {
return;
}

// Make sure this is not a method call.
$prev = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true);
if (
$tokens[$prev]['code'] === T_OBJECT_OPERATOR
|| $tokens[$prev]['code'] === T_DOUBLE_COLON
|| $tokens[$prev]['code'] === T_NULLSAFE_OBJECT_OPERATOR
) {
return;
}

// If the next non-whitespace token after this token
// is not an opening parenthesis then it is not a function call.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
if ($openBracket === false) {
return;
}

// The next non-whitespace token must be the constant name.
$constPtr = $phpcsFile->findNext(T_WHITESPACE, $openBracket + 1, null, true);
if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
return;
}

$constName = $tokens[$constPtr]['content'];

// Check for constants like self::CONSTANT.
$prefix = '';
$splitPos = strpos($constName, '::');
if ($splitPos !== false) {
$prefix = substr($constName, 0, $splitPos + 2);
$constName = substr($constName, $splitPos + 2);
}

// Strip namespace from constant like /foo/bar/CONSTANT.
$splitPos = strrpos($constName, '\\');
if ($splitPos !== false) {
$prefix = substr($constName, 0, $splitPos + 1);
$constName = substr($constName, $splitPos + 1);
}

if ($this->matchesRegex($constName, $this->pattern)) {
return;
}

$error = sprintf('Constant "%%s" does not match pattern "%s"', $this->pattern);
$data = [
$prefix . $constName,
];
$phpcsFile->addError($error, $stackPtr, self::CodeConstantNotMatchPattern, $data);
}

private function matchesRegex(string $varName, string $pattern): bool
{
return preg_match(sprintf('~%s~', $pattern), $varName) === 1;
}
}
26 changes: 13 additions & 13 deletions src/Cdn77/Sniffs/NamingConventions/ValidVariableNameSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@

class ValidVariableNameSniff extends AbstractVariableSniff
{
public const CODE_DOES_NOT_MATCH_PATTERN = 'DoesNotMatchPattern';
public const CODE_MEMBER_DOES_NOT_MATCH_PATTERN = 'MemberDoesNotMatchPattern';
public const CODE_STRING_DOES_NOT_MATCH_PATTERN = 'StringDoesNotMatchPattern';
private const PATTERN_CAMEL_CASE = '\b([a-zA-Z][a-zA-Z0-9]*?([A-Z][a-zA-Z0-9]*?)*?)\b';
private const PATTERN_CAMEL_CASE_OR_UNUSED = '\b(([a-zA-Z][a-zA-Z0-9]*?([A-Z][a-zA-Z0-9]*?)*?)|_+)\b';
public const CodeDoesNotMatchPattern = 'DoesNotMatchPattern';
public const CodeMemberDoesNotMatchPattern = 'MemberDoesNotMatchPattern';
public const CodeStringDoesNotMatchPattern = 'StringDoesNotMatchPattern';
private const PatternCamelCase = '\b([a-zA-Z][a-zA-Z0-9]*?([A-Z][a-zA-Z0-9]*?)*?)\b';
private const PatternCamelCaseOrUnused = '\b(([a-zA-Z][a-zA-Z0-9]*?([A-Z][a-zA-Z0-9]*?)*?)|_+)\b';

public string $pattern = self::PATTERN_CAMEL_CASE_OR_UNUSED;
public string $memberPattern = self::PATTERN_CAMEL_CASE;
public string $stringPattern = self::PATTERN_CAMEL_CASE;
public string $pattern = self::PatternCamelCaseOrUnused;
public string $memberPattern = self::PatternCamelCase;
public string $stringPattern = self::PatternCamelCase;

/**
* Processes this test, when one of its tokens is encountered.
Expand Down Expand Up @@ -69,7 +69,7 @@ protected function processVariable(File $phpcsFile, $stackPtr): void
if (! $this->matchesRegex($objVarName, $this->memberPattern)) {
$error = sprintf('Member variable "%%s" does not match pattern "%s"', $this->memberPattern);
$data = [$objVarName];
$phpcsFile->addError($error, $var, self::CODE_MEMBER_DOES_NOT_MATCH_PATTERN, $data);
$phpcsFile->addError($error, $var, self::CodeMemberDoesNotMatchPattern, $data);
}
}
}
Expand All @@ -80,7 +80,7 @@ protected function processVariable(File $phpcsFile, $stackPtr): void
if (! $this->matchesRegex($varName, $this->memberPattern)) {
$error = sprintf('Member variable "%%s" does not match pattern "%s"', $this->memberPattern);
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addError($error, $stackPtr, self::CODE_MEMBER_DOES_NOT_MATCH_PATTERN, $data);
$phpcsFile->addError($error, $stackPtr, self::CodeMemberDoesNotMatchPattern, $data);
}

return;
Expand All @@ -92,7 +92,7 @@ protected function processVariable(File $phpcsFile, $stackPtr): void

$error = sprintf('Variable "%%s" does not match pattern "%s"', $this->pattern);
$data = [$varName];
$phpcsFile->addError($error, $stackPtr, self::CODE_DOES_NOT_MATCH_PATTERN, $data);
$phpcsFile->addError($error, $stackPtr, self::CodeDoesNotMatchPattern, $data);
}

/**
Expand Down Expand Up @@ -124,7 +124,7 @@ protected function processMemberVar(File $phpcsFile, $stackPtr): void
}

$error = sprintf('Member variable "%%s" does not match pattern "%s"', $this->memberPattern);
$phpcsFile->addError($error, $stackPtr, self::CODE_MEMBER_DOES_NOT_MATCH_PATTERN, $errorData);
$phpcsFile->addError($error, $stackPtr, self::CodeMemberDoesNotMatchPattern, $errorData);
}

/**
Expand Down Expand Up @@ -161,7 +161,7 @@ protected function processVariableInString(File $phpcsFile, $stackPtr): void

$error = sprintf('Variable "%%s" does not match pattern "%s"', $this->stringPattern);
$data = [$varName];
$phpcsFile->addError($error, $stackPtr, self::CODE_STRING_DOES_NOT_MATCH_PATTERN, $data);
$phpcsFile->addError($error, $stackPtr, self::CodeStringDoesNotMatchPattern, $data);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Cdn77/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamType"/>

<!-- replaced by Cdn77.NamingConventions.ValidConstantName -->
<exclude name="Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase" />

<!-- $_ variables are used to tell e.g. psalm that the variable is intentionally unused. -->
<!-- replaced by Cdn77.NamingConventions.ValidVariableName -->
<exclude name="Squiz.NamingConventions.ValidVariableName" />
Expand All @@ -39,6 +42,7 @@
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification" />
</rule>

<rule ref="Cdn77.NamingConventions.ValidConstantName"/>
<rule ref="Cdn77.NamingConventions.ValidVariableName"/>

<rule ref="SlevomatCodingStandard.Classes.ClassStructure">
Expand Down
42 changes: 42 additions & 0 deletions tests/Sniffs/NamingConventions/ValidConstantNameSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Cdn77\Sniffs\NamingConventions;

use Cdn77\TestCase;

use function array_keys;
use function json_encode;

use const JSON_THROW_ON_ERROR;

class ValidConstantNameSniffTest extends TestCase
{
public function testErrors(): void
{
$file = self::checkFile(__DIR__ . '/data/ValidConstantNameTest.inc');

$errorTypesPerLine = [
7 => ValidConstantNameSniff::CodeConstantNotMatchPattern,
8 => ValidConstantNameSniff::CodeConstantNotMatchPattern,
10 => ValidConstantNameSniff::CodeConstantNotMatchPattern,
11 => ValidConstantNameSniff::CodeConstantNotMatchPattern,
17 => ValidConstantNameSniff::CodeClassConstantNotMatchPattern,
18 => ValidConstantNameSniff::CodeClassConstantNotMatchPattern,
19 => ValidConstantNameSniff::CodeClassConstantNotMatchPattern,
];
$possibleLines = array_keys($errorTypesPerLine);

$errors = $file->getErrors();
foreach ($errors as $line => $error) {
self::assertContains($line, $possibleLines, json_encode($error, JSON_THROW_ON_ERROR));

$errorType = $errorTypesPerLine[$line];

self::assertSniffError($file, $line, $errorType);
}

self::assertSame(6, $file->getErrorCount());
}
}
78 changes: 39 additions & 39 deletions tests/Sniffs/NamingConventions/ValidVariableNameSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,47 @@ public function testErrors(): void
$file = self::checkFile(__DIR__ . '/data/ValidVariableNameSniffTest.inc');

$errorTypesPerLine = [
3 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
5 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
10 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
12 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
15 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
17 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
19 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
20 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
21 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
26 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
28 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
31 => ValidVariableNameSniff::CODE_STRING_DOES_NOT_MATCH_PATTERN,
32 => ValidVariableNameSniff::CODE_STRING_DOES_NOT_MATCH_PATTERN,
34 => ValidVariableNameSniff::CODE_STRING_DOES_NOT_MATCH_PATTERN,
37 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
39 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
48 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
50 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
53 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
55 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
57 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
58 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
59 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
62 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
76 => ValidVariableNameSniff::CODE_STRING_DOES_NOT_MATCH_PATTERN,
100 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
101 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
102 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
117 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
118 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
128 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
132 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
134 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
135 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
140 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
142 => ValidVariableNameSniff::CODE_MEMBER_DOES_NOT_MATCH_PATTERN,
3 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
5 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
10 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
12 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
15 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
17 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
19 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
20 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
21 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
26 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
28 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
31 => ValidVariableNameSniff::CodeStringDoesNotMatchPattern,
32 => ValidVariableNameSniff::CodeStringDoesNotMatchPattern,
34 => ValidVariableNameSniff::CodeStringDoesNotMatchPattern,
37 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
39 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
48 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
50 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
53 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
55 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
57 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
58 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
59 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
62 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
76 => ValidVariableNameSniff::CodeStringDoesNotMatchPattern,
100 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
101 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
102 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
117 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
118 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
128 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
132 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
134 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
135 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
140 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
142 => ValidVariableNameSniff::CodeMemberDoesNotMatchPattern,
144 => [
ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
ValidVariableNameSniff::CodeDoesNotMatchPattern,
ValidVariableNameSniff::CodeDoesNotMatchPattern,
],
146 => ValidVariableNameSniff::CODE_DOES_NOT_MATCH_PATTERN,
146 => ValidVariableNameSniff::CodeDoesNotMatchPattern,
];
$possibleLines = array_keys($errorTypesPerLine);

Expand Down
24 changes: 24 additions & 0 deletions tests/Sniffs/NamingConventions/data/ValidConstantNameTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
use Exception as My_Exception, foo\bar, baz;
namespace foo;
namespace foo\bar;
namespace bar\foo\baz;

define('SCREAMING_SNAKE_CASE', true);
define('camelCase', true);
define('PascalCase', true);
define('bar\foo\baz\SCREAMING_SNAKE_CASE_WITH_NAMESPACE', true);
define('bar\foo\baz\camelCaseWithNamespace', true);
define("bar\foo\baz\PascalCaseWithNamespace", true);

class TestClass extends MyClass implements MyInterface, YourInterface
{

const SCREAMING_SNAKE_CASE = 'hello';
const camelCase = 'hello';
const PascalCase = 'hello';
}

$foo->define('bar');
$foo->getBar()->define('foo');
Foo::define('bar');

0 comments on commit 5d22d74

Please sign in to comment.