├── CakePHP
├── Sniffs
│ ├── Classes
│ │ └── ReturnTypeHintSniff.php
│ ├── Commenting
│ │ ├── DocBlockAlignmentSniff.php
│ │ ├── FunctionCommentSniff.php
│ │ ├── InheritDocSniff.php
│ │ └── TypeHintSniff.php
│ ├── ControlStructures
│ │ ├── ControlStructuresSniff.php
│ │ ├── ElseIfDeclarationSniff.php
│ │ └── WhileStructuresSniff.php
│ ├── Formatting
│ │ └── BlankLineBeforeReturnSniff.php
│ ├── NamingConventions
│ │ ├── ValidFunctionNameSniff.php
│ │ └── ValidTraitNameSniff.php
│ ├── PHP
│ │ ├── DisallowShortOpenTagSniff.php
│ │ └── SingleQuoteSniff.php
│ └── WhiteSpace
│ │ ├── EmptyLinesSniff.php
│ │ ├── FunctionCallSpacingSniff.php
│ │ ├── FunctionClosingBraceSpaceSniff.php
│ │ ├── FunctionOpeningBraceSpaceSniff.php
│ │ ├── FunctionSpacingSniff.php
│ │ └── TabAndSpaceSniff.php
└── ruleset.xml
├── LICENSE
├── README.md
├── composer.json
├── docs
├── README.md
└── generate.php
└── tests
└── phpstan_bootstrap.php
/CakePHP/Sniffs/Classes/ReturnTypeHintSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
39 |
40 | $openParenthesisIndex = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr + 1);
41 | $closeParenthesisIndex = $tokens[$openParenthesisIndex]['parenthesis_closer'];
42 |
43 | $colonIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenthesisIndex + 1, null, true);
44 | if (!$colonIndex) {
45 | return;
46 | }
47 |
48 | $startIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $colonIndex + 1, $colonIndex + 3, true);
49 | if (!$startIndex) {
50 | return;
51 | }
52 |
53 | if (!$this->isChainingMethod($phpcsFile, $stackPtr)) {
54 | $this->assertNotThisOrStatic($phpcsFile, $stackPtr);
55 |
56 | return;
57 | }
58 |
59 | // We skip for interface methods
60 | if (empty($tokens[$stackPtr]['scope_opener']) || empty($tokens[$stackPtr]['scope_closer'])) {
61 | return;
62 | }
63 |
64 | $returnTokenCode = $tokens[$startIndex]['code'];
65 | if ($returnTokenCode !== T_SELF) {
66 | // Then we can only warn, but not auto-fix
67 | $phpcsFile->addError(
68 | 'Chaining methods (@return $this) should not have any return-type-hint.',
69 | $startIndex,
70 | 'InvalidSelf',
71 | );
72 |
73 | return;
74 | }
75 |
76 | $fix = $phpcsFile->addFixableError(
77 | 'Chaining methods (@return $this) should not have any return-type-hint (Remove "self").',
78 | $startIndex,
79 | 'InvalidSelf',
80 | );
81 | if (!$fix) {
82 | return;
83 | }
84 |
85 | $phpcsFile->fixer->beginChangeset();
86 | for ($i = $colonIndex; $i <= $startIndex; $i++) {
87 | $phpcsFile->fixer->replaceToken($i, '');
88 | }
89 | $phpcsFile->fixer->endChangeset();
90 | }
91 |
92 | /**
93 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
94 | * @param int $stackPointer Stack pointer
95 | * @return bool
96 | */
97 | protected function isChainingMethod(File $phpCsFile, int $stackPointer): bool
98 | {
99 | $docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);
100 |
101 | if (!$docBlockEndIndex) {
102 | return false;
103 | }
104 |
105 | $tokens = $phpCsFile->getTokens();
106 |
107 | $docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];
108 |
109 | for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
110 | if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
111 | continue;
112 | }
113 | if ($tokens[$i]['content'] !== '@return') {
114 | continue;
115 | }
116 |
117 | $classNameIndex = $i + 2;
118 |
119 | if ($tokens[$classNameIndex]['code'] !== T_DOC_COMMENT_STRING) {
120 | continue;
121 | }
122 |
123 | $content = $tokens[$classNameIndex]['content'];
124 | if (!$content) {
125 | continue;
126 | }
127 |
128 | return $content === '$this';
129 | }
130 |
131 | return false;
132 | }
133 |
134 | /**
135 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
136 | * @param int $stackPointer Stack pointer
137 | * @return void
138 | */
139 | protected function assertNotThisOrStatic(File $phpCsFile, int $stackPointer): void
140 | {
141 | $docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);
142 |
143 | if (!$docBlockEndIndex) {
144 | return;
145 | }
146 |
147 | $tokens = $phpCsFile->getTokens();
148 |
149 | $docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];
150 |
151 | for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
152 | if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
153 | continue;
154 | }
155 | if ($tokens[$i]['content'] !== '@return') {
156 | continue;
157 | }
158 |
159 | $classNameIndex = $i + 2;
160 |
161 | if ($tokens[$classNameIndex]['code'] !== T_DOC_COMMENT_STRING) {
162 | continue;
163 | }
164 |
165 | $content = $tokens[$classNameIndex]['content'];
166 | if (!$content || strpos($content, '\\') !== 0) {
167 | continue;
168 | }
169 |
170 | $classNameWithNamespace = $this->getClassNameWithNamespace($phpCsFile);
171 | if ($content !== $classNameWithNamespace) {
172 | continue;
173 | }
174 |
175 | $phpCsFile->addError(
176 | 'Class name repeated, expected `self` or `$this`.',
177 | $classNameIndex,
178 | 'InvalidClass',
179 | );
180 | }
181 | }
182 |
183 | /**
184 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
185 | * @param int $stackPointer Stack pointer
186 | * @return int|null Stackpointer value of docblock end tag, or null if cannot be found
187 | */
188 | protected function findRelatedDocBlock(File $phpCsFile, int $stackPointer): ?int
189 | {
190 | $tokens = $phpCsFile->getTokens();
191 |
192 | $line = $tokens[$stackPointer]['line'];
193 | $beginningOfLine = $stackPointer;
194 | while (!empty($tokens[$beginningOfLine - 1]) && $tokens[$beginningOfLine - 1]['line'] === $line) {
195 | $beginningOfLine--;
196 | }
197 |
198 | if (
199 | !empty($tokens[$beginningOfLine - 2])
200 | && $tokens[$beginningOfLine - 2]['code'] === T_DOC_COMMENT_CLOSE_TAG
201 | ) {
202 | return $beginningOfLine - 2;
203 | }
204 |
205 | if (
206 | !empty($tokens[$beginningOfLine - 3])
207 | && $tokens[$beginningOfLine - 3]['code'] === T_DOC_COMMENT_CLOSE_TAG
208 | ) {
209 | return $beginningOfLine - 3;
210 | }
211 |
212 | return null;
213 | }
214 |
215 | /**
216 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
217 | * @return string|null
218 | */
219 | protected function getClassNameWithNamespace(File $phpCsFile): ?string
220 | {
221 | try {
222 | $lastToken = TokenHelper::getLastTokenPointer($phpCsFile);
223 | } catch (EmptyFileException $e) {
224 | return null;
225 | }
226 |
227 | if (!NamespaceHelper::findCurrentNamespaceName($phpCsFile, $lastToken)) {
228 | return null;
229 | }
230 |
231 | $classPointer = $phpCsFile->findPrevious(
232 | [T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM],
233 | $lastToken,
234 | );
235 | if (!$classPointer) {
236 | return null;
237 | }
238 |
239 | return ClassHelper::getFullyQualifiedName(
240 | $phpCsFile,
241 | $classPointer,
242 | );
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/Commenting/DocBlockAlignmentSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
40 | $commentClose = $phpcsFile->findNext(T_DOC_COMMENT_CLOSE_TAG, $stackPtr);
41 | $afterComment = $phpcsFile->findNext(T_WHITESPACE, $commentClose + 1, null, true);
42 | $commentIndentation = $tokens[$stackPtr]['column'] - 1;
43 | $codeIndentation = $tokens[$afterComment]['column'] - 1;
44 |
45 | // Check for doc block opening being misaligned
46 | if ($commentIndentation != $codeIndentation) {
47 | $msg = 'Doc block not aligned with code; expected indentation of %s but found %s';
48 | $data = [$codeIndentation, $commentIndentation];
49 | $fix = $phpcsFile->addFixableError($msg, $stackPtr, 'DocBlockMisaligned', $data);
50 | if ($fix === true) {
51 | // Collect tokens to change indentation of
52 | $tokensToIndent = [
53 | $stackPtr => $codeIndentation,
54 | ];
55 | $commentOpenLine = $tokens[$stackPtr]['line'];
56 | $commentCloseLine = $tokens[$commentClose]['line'];
57 | $lineBreaksInComment = $commentCloseLine - $commentOpenLine;
58 | if ($lineBreaksInComment !== 0) {
59 | $searchToken = $stackPtr + 1;
60 | do {
61 | $commentBorder = $phpcsFile->findNext(
62 | [T_DOC_COMMENT_STAR, T_DOC_COMMENT_CLOSE_TAG],
63 | $searchToken,
64 | $commentClose + 1,
65 | );
66 | if ($commentBorder !== false) {
67 | $tokensToIndent[$commentBorder] = $codeIndentation + 1;
68 | $searchToken = $commentBorder + 1;
69 | }
70 | } while ($commentBorder !== false);
71 | }
72 |
73 | // Update indentation
74 | $phpcsFile->fixer->beginChangeset();
75 | foreach ($tokensToIndent as $searchToken => $indent) {
76 | $indentString = str_repeat(' ', $indent);
77 | $isOpenTag = $tokens[$searchToken]['code'] === T_DOC_COMMENT_OPEN_TAG;
78 | if ($isOpenTag && $commentIndentation === 0) {
79 | $phpcsFile->fixer->addContentBefore($searchToken, $indentString);
80 | } else {
81 | $phpcsFile->fixer->replaceToken($searchToken - 1, $indentString);
82 | }
83 | }
84 | $phpcsFile->fixer->endChangeset();
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php:
--------------------------------------------------------------------------------
1 |
25 | *
A comment exists
26 | * No spacing between doc comment and function
27 | * Any throw tag must have a comment
28 | *
29 | *
30 | * @category PHP
31 | * @package PHP_CodeSniffer
32 | * @author Greg Sherwood
33 | * @author Marc McIntyre
34 | * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
35 | * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
36 | * @version Release: @package_version@
37 | * @link https://pear.php.net/package/PHP_CodeSniffer
38 | */
39 | class FunctionCommentSniff implements Sniff
40 | {
41 | /**
42 | * Returns an array of tokens this test wants to listen for.
43 | *
44 | * @return array
45 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
46 | */
47 | public function register()
48 | {
49 | return [T_FUNCTION];
50 | }
51 |
52 | /**
53 | * @inheritDoc
54 | */
55 | public function process(File $phpcsFile, $stackPtr)
56 | {
57 | $tokens = $phpcsFile->getTokens();
58 |
59 | $docCommentEnd = $phpcsFile->findPrevious(
60 | [T_DOC_COMMENT_CLOSE_TAG, T_SEMICOLON, T_CLOSE_CURLY_BRACKET, T_OPEN_CURLY_BRACKET],
61 | $stackPtr - 1,
62 | null,
63 | );
64 | if ($docCommentEnd === false || $tokens[$docCommentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
65 | $phpcsFile->addError(
66 | 'Missing doc comment for function %s()',
67 | $stackPtr,
68 | 'Missing',
69 | [$phpcsFile->getDeclarationName($stackPtr)],
70 | );
71 |
72 | return;
73 | }
74 |
75 | $lastEndToken = $docCommentEnd;
76 | do {
77 | $attribute = $phpcsFile->findNext(
78 | [T_ATTRIBUTE],
79 | $lastEndToken + 1,
80 | $stackPtr,
81 | );
82 | if ($attribute !== false) {
83 | if ($tokens[$lastEndToken]['line'] !== $tokens[$attribute]['line'] - 1) {
84 | $phpcsFile->addError(
85 | 'There must be no blank lines after the function comment or attribute',
86 | $lastEndToken,
87 | 'SpacingAfter',
88 | );
89 |
90 | return;
91 | }
92 |
93 | $lastEndToken = $tokens[$attribute]['attribute_closer'];
94 | }
95 | } while ($attribute !== false);
96 |
97 | if ($tokens[$lastEndToken]['line'] !== $tokens[$stackPtr]['line'] - 1) {
98 | $phpcsFile->addError(
99 | 'There must be no blank lines after the function comment or attribute',
100 | $lastEndToken,
101 | 'SpacingAfter',
102 | );
103 | }
104 |
105 | $commentStart = $tokens[$docCommentEnd]['comment_opener'];
106 | $this->processTagSpacing($phpcsFile, $stackPtr, $commentStart);
107 | $this->processThrows($phpcsFile, $stackPtr, $commentStart);
108 | }
109 |
110 | /**
111 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
112 | * @param int $stackPtr The position of the current token in the stack passed in $tokens.
113 | * @return void
114 | */
115 | protected function processTagSpacing(File $phpcsFile, int $stackPtr, int $commentStart): void
116 | {
117 | $tokens = $phpcsFile->getTokens();
118 | $tags = $tokens[$commentStart]['comment_tags'];
119 | foreach ($tags as $tag) {
120 | if (
121 | $tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING &&
122 | $phpcsFile->fixer->getTokenContent($tag + 1) !== ' '
123 | ) {
124 | $fix = $phpcsFile->addFixableWarning('Should be only one space after tag', $tag, 'TagAlignment');
125 | if ($fix) {
126 | $phpcsFile->fixer->beginChangeset();
127 | $phpcsFile->fixer->replaceToken($tag + 1, ' ');
128 | $phpcsFile->fixer->endChangeset();
129 | }
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * Process any throw tags that this function comment has.
136 | *
137 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
138 | * @param int $stackPtr The position of the current token in the stack passed in $tokens.
139 | * @param int $commentStart The position in the stack where the comment started.
140 | * @return void
141 | */
142 | protected function processThrows(File $phpcsFile, int $stackPtr, int $commentStart): void
143 | {
144 | $tokens = $phpcsFile->getTokens();
145 |
146 | foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
147 | if ($tokens[$tag]['content'] !== '@throws') {
148 | continue;
149 | }
150 |
151 | $exception = null;
152 | if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) {
153 | $matches = [];
154 | preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[$tag + 2]['content'], $matches);
155 | $exception = $matches[1] ?? null;
156 | }
157 |
158 | if ($exception === null) {
159 | $error = 'Exception type and comment missing for @throws tag in function comment';
160 | $phpcsFile->addWarning($error, $tag, 'InvalidThrows');
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/Commenting/InheritDocSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
40 |
41 | $commentEnd = $tokens[$stackPtr]['comment_closer'];
42 |
43 | $empty = [
44 | T_DOC_COMMENT_WHITESPACE,
45 | T_DOC_COMMENT_STAR,
46 | ];
47 |
48 | $inheritDoc = $phpcsFile->findNext($empty, $stackPtr + 1, $commentEnd, true);
49 | if ($inheritDoc === false) {
50 | // Ignore empty comments
51 | return;
52 | }
53 |
54 | if (
55 | preg_match('/@inheritDoc/i', $tokens[$inheritDoc]['content']) === 1 &&
56 | preg_match('/@inheritDoc/', $tokens[$inheritDoc]['content']) === 0
57 | ) {
58 | $msg = 'inheritDoc is not capitalized correctly';
59 | $fix = $phpcsFile->addFixableWarning($msg, $inheritDoc, 'BadSpelling');
60 | if ($fix === true) {
61 | $fixed = preg_replace('/inheritDoc/i', 'inheritDoc', $tokens[$inheritDoc]['content']);
62 | $phpcsFile->fixer->replaceToken($inheritDoc, $fixed);
63 | }
64 | }
65 |
66 | if (
67 | preg_match('/^@inheritDoc/i', $tokens[$inheritDoc]['content']) === 1 &&
68 | ( preg_match('/^@inheritDoc$/i', $tokens[$inheritDoc]['content']) !== 1 ||
69 | $phpcsFile->findNext($empty, $inheritDoc + 1, $commentEnd, true) !== false
70 | )
71 | ) {
72 | $msg = 'When using @inheritDoc, it must be the only doc comment.';
73 | $phpcsFile->addWarning($msg, $inheritDoc, 'NotEmpty');
74 | }
75 |
76 | if (
77 | preg_match('/^{@inheritDoc}$/i', $tokens[$inheritDoc]['content']) === 1 &&
78 | $phpcsFile->findNext($empty, $inheritDoc + 1, $commentEnd, true) === false
79 | ) {
80 | $msg = 'When inheriting entire doc comment, @inheritDoc must be used instead of {@inheritDoc}.';
81 | $fix = $phpcsFile->addFixableWarning($msg, $inheritDoc, 'ShouldNotWrap');
82 | if ($fix === true) {
83 | $phpcsFile->fixer->replaceToken($inheritDoc, '@inheritDoc');
84 | }
85 | }
86 |
87 | if (
88 | preg_match('/^{@inheritDoc}/i', $tokens[$inheritDoc]['content']) === 1 &&
89 | preg_match('/^{@inheritDoc}$/i', $tokens[$inheritDoc]['content']) !== 1
90 | ) {
91 | $msg = 'If using {@inheritDoc} to copy description, it must be the first line in doc comment.';
92 | $phpcsFile->addWarning($msg, $inheritDoc, 'FirstLine');
93 | }
94 |
95 | $nextComment = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $inheritDoc + 1, $commentEnd);
96 | while ($nextComment !== false) {
97 | if (preg_match('/^{@inheritDoc}$/i', $tokens[$nextComment]['content']) === 1) {
98 | $msg = 'If using {@inheritDoc} to copy description, it must be the first line in doc comment.';
99 | $phpcsFile->addWarning($msg, $nextComment, 'FirstLine');
100 | }
101 | $nextComment = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $nextComment + 1, $commentEnd);
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/Commenting/TypeHintSniff.php:
--------------------------------------------------------------------------------
1 |
56 | */
57 | protected static array $typeHintTags = [
58 | '@var',
59 | '@psalm-var',
60 | '@phpstan-var',
61 | '@param',
62 | '@psalm-param',
63 | '@phpstan-param',
64 | '@return',
65 | '@psalm-return',
66 | '@phpstan-return',
67 | ];
68 |
69 | /**
70 | * Returns an array of tokens this test wants to listen for.
71 | *
72 | * @return array
73 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint
74 | */
75 | public function register()
76 | {
77 | return [T_DOC_COMMENT_OPEN_TAG];
78 | }
79 |
80 | /**
81 | * @inheritDoc
82 | */
83 | public function process(File $phpcsFile, $stackPtr)
84 | {
85 | $tokens = $phpcsFile->getTokens();
86 |
87 | if (!isset($tokens[$stackPtr]['comment_closer'])) {
88 | return;
89 | }
90 |
91 | foreach ($tokens[$stackPtr]['comment_tags'] as $tag) {
92 | if (
93 | $tokens[$tag + 2]['code'] !== T_DOC_COMMENT_STRING ||
94 | !in_array($tokens[$tag]['content'], self::$typeHintTags, true)
95 | ) {
96 | continue;
97 | }
98 |
99 | $tagComment = $phpcsFile->fixer->getTokenContent($tag + 2);
100 | $valueNode = self::getValueNode($tokens[$tag]['content'], $tagComment);
101 | if ($valueNode instanceof InvalidTagValueNode) {
102 | continue;
103 | }
104 |
105 | if (isset($valueNode->type)) {
106 | if ($valueNode->type instanceof UnionTypeNode) {
107 | $types = $valueNode->type->types;
108 | } elseif ($valueNode->type instanceof ArrayTypeNode) {
109 | $types = [$valueNode->type];
110 | } else {
111 | continue;
112 | }
113 | } else {
114 | $phpcsFile->addWarning('@param type hint is missing', $tag, 'MissingParamType');
115 | continue;
116 | }
117 |
118 | if ($this->ignorePhpStormGenerics && $this->isPhpStormGenericType($types)) {
119 | continue;
120 | }
121 |
122 | $originalTypeHint = $this->renderUnionTypes($types);
123 | $sortedTypeHint = $this->getSortedTypeHint($types);
124 | if ($sortedTypeHint === $originalTypeHint) {
125 | continue;
126 | }
127 |
128 | $fix = $phpcsFile->addFixableWarning(
129 | '%s type hint is not formatted properly, expected "%s"',
130 | $tag,
131 | 'IncorrectFormat',
132 | [$tokens[$tag]['content'], $sortedTypeHint],
133 | );
134 | if (!$fix) {
135 | continue;
136 | }
137 |
138 | $newComment = $tagComment;
139 | if ($valueNode instanceof VarTagValueNode) {
140 | $newComment = trim(sprintf(
141 | '%s %s %s',
142 | $sortedTypeHint,
143 | $valueNode->variableName,
144 | $valueNode->description,
145 | ));
146 | if ($tagComment[-1] === ' ') {
147 | // tags above variables in code have a trailing space
148 | $newComment .= ' ';
149 | }
150 | } elseif ($valueNode instanceof ParamTagValueNode) {
151 | $newComment = trim(sprintf(
152 | '%s %s%s %s',
153 | $sortedTypeHint,
154 | $valueNode->isVariadic ? '...' : '',
155 | $valueNode->parameterName,
156 | $valueNode->description,
157 | ));
158 | } elseif ($valueNode instanceof ReturnTagValueNode) {
159 | $newComment = trim(sprintf(
160 | '%s %s',
161 | $sortedTypeHint,
162 | $valueNode->description,
163 | ));
164 | }
165 |
166 | $phpcsFile->fixer->beginChangeset();
167 | $phpcsFile->fixer->replaceToken($tag + 2, $newComment);
168 | $phpcsFile->fixer->endChangeset();
169 | }
170 | }
171 |
172 | /**
173 | * @param array $types node types
174 | * @return bool
175 | */
176 | protected function isPhpStormGenericType(array $types): bool
177 | {
178 | if (count($types) != 2) {
179 | return false;
180 | }
181 |
182 | return $types[0] instanceof IdentifierTypeNode &&
183 | $types[1] instanceof ArrayTypeNode &&
184 | $types[0]->name[0] === '\\';
185 | }
186 |
187 | /**
188 | * @param array $types node types
189 | * @return string
190 | */
191 | protected function getSortedTypeHint(array $types): string
192 | {
193 | static $shouldSort = [
194 | '\\Closure',
195 | '\\Generator',
196 | '\\ArrayObject',
197 | '\\ArrayAccess',
198 | '\\Traversable',
199 | '\\Stringable',
200 | '\\Countable',
201 | '$this',
202 | 'self',
203 | 'mixed',
204 | 'callable',
205 | 'resource',
206 | 'object',
207 | 'iterable',
208 | 'list',
209 | 'array',
210 | 'callable-string',
211 | 'class-string',
212 | 'interface-string',
213 | 'scalar',
214 | 'string',
215 | 'float',
216 | 'int',
217 | 'bool',
218 | 'true',
219 | 'false',
220 | 'null',
221 | 'void',
222 | ];
223 |
224 | $sortable = array_fill_keys($shouldSort, []);
225 | $unsortable = [];
226 | foreach ($types as $type) {
227 | $sortName = null;
228 | if ($type instanceof IdentifierTypeNode) {
229 | $sortName = $type->name;
230 | } elseif ($type instanceof NullableTypeNode) {
231 | if ($type->type instanceof IdentifierTypeNode) {
232 | $sortName = $type->type->name;
233 | }
234 | } elseif ($type instanceof ArrayTypeNode) {
235 | if ($this->convertArraysToGenerics) {
236 | $type = new GenericTypeNode(new IdentifierTypeNode('array'), [$type->type]);
237 | $sortName = 'array';
238 | } elseif ($type->type instanceof IdentifierTypeNode) {
239 | $sortName = $type->type->name;
240 | } else {
241 | $sortName = 'array';
242 | }
243 | } elseif ($type instanceof ArrayShapeNode) {
244 | $sortName = 'array';
245 | } elseif ($type instanceof GenericTypeNode) {
246 | if (in_array($type->type->name, $shouldSort)) {
247 | $sortName = $type->type->name;
248 | }
249 | }
250 |
251 | if (!$sortName) {
252 | $unsortable[] = $type;
253 | continue;
254 | }
255 |
256 | if (in_array($sortName, $shouldSort, true)) {
257 | if ($type instanceof ArrayTypeNode) {
258 | array_unshift($sortable[$sortName], $type);
259 | } else {
260 | $sortable[$sortName][] = $type;
261 | }
262 | } else {
263 | $unsortable[] = $type;
264 | }
265 | }
266 |
267 | $sorted = [];
268 | array_walk($sortable, function ($types) use (&$sorted): void {
269 | $sorted = array_merge($sorted, $types);
270 | });
271 |
272 | return $this->renderUnionTypes(array_merge($unsortable, $sorted));
273 | }
274 |
275 | /**
276 | * @param array<\PHPStan\PhpDocParser\Ast\Type\TypeNode> $typeNodes type nodes
277 | * @return string
278 | */
279 | protected function renderUnionTypes(array $typeNodes): string
280 | {
281 | // Remove parenthesis added by phpstan around union and intersection types
282 | return (string)preg_replace(
283 | ['/ ([\|&]) /', '/<\(/', '/\)>/', '/\), /', '/, \(/'],
284 | ['${1}', '<', '>', ', ', ', '],
285 | implode('|', $typeNodes),
286 | );
287 | }
288 |
289 | /**
290 | * @param string $tagName tag name
291 | * @param string $tagComment tag comment
292 | * @return \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode
293 | */
294 | protected static function getValueNode(string $tagName, string $tagComment): PhpDocTagValueNode
295 | {
296 | static $phpDocParser;
297 | if (!$phpDocParser) {
298 | $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]);
299 |
300 | $constExprParser = new ConstExprParser($config);
301 | $phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser);
302 | }
303 |
304 | static $phpDocLexer;
305 | if (!$phpDocLexer) {
306 | $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]);
307 | $phpDocLexer = new Lexer($config);
308 | }
309 |
310 | return $phpDocParser->parseTagValue(new TokenIterator($phpDocLexer->tokenize($tagComment)), $tagName);
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/ControlStructures/ControlStructuresSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
42 |
43 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
44 | if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) {
45 | $closer = $tokens[$nextToken]['parenthesis_closer'];
46 | $diff = $closer - $stackPtr;
47 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + $diff + 1), null, true);
48 | }
49 | if ($tokens[$nextToken]['code'] === T_IF) {
50 | // "else if" is not checked by this sniff, another sniff takes care of that.
51 | return;
52 | }
53 | if ($tokens[$nextToken]['code'] !== T_OPEN_CURLY_BRACKET && $tokens[$nextToken]['code'] !== T_COLON) {
54 | $error = 'Curly brackets required for if/elseif/else.';
55 | $phpcsFile->addError($error, $stackPtr, 'NotAllowed');
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/ControlStructures/ElseIfDeclarationSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
40 |
41 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
42 | if ($tokens[$nextToken]['code'] !== T_IF) {
43 | return;
44 | }
45 |
46 | $error = 'Usage of ELSE IF not allowed; use ELSEIF instead';
47 | $phpcsFile->addError($error, $stackPtr, 'NotAllowed');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/ControlStructures/WhileStructuresSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
40 |
41 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
42 | if ($tokens[$nextToken]['code'] === T_OPEN_PARENTHESIS) {
43 | $closer = $tokens[$nextToken]['parenthesis_closer'];
44 | $diff = $closer - $stackPtr;
45 | $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + $diff + 1), null, true);
46 | }
47 |
48 | if ($tokens[$stackPtr]['code'] === T_WHILE && $tokens[$nextToken]['code'] === T_SEMICOLON) {
49 | /* This while is probably part of a do-while construction, skip it .. */
50 | return;
51 | }
52 | if ($tokens[$nextToken]['code'] !== T_OPEN_CURLY_BRACKET && $tokens[$nextToken]['code'] !== T_COLON) {
53 | $error = 'Curly brackets required in a do-while or while loop';
54 | $phpcsFile->addError($error, $stackPtr, 'NotAllowed');
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/Formatting/BlankLineBeforeReturnSniff.php:
--------------------------------------------------------------------------------
1 |
25 | * @license https://spdx.org/licenses/MIT MIT License
26 | * @link https://github.com/escapestudios/Symfony2-coding-standard
27 | */
28 | class BlankLineBeforeReturnSniff implements Sniff
29 | {
30 | /**
31 | * @inheritDoc
32 | */
33 | public function register()
34 | {
35 | return [T_RETURN];
36 | }
37 |
38 | /**
39 | * @inheritDoc
40 | */
41 | public function process(File $phpcsFile, $stackPtr)
42 | {
43 | $tokens = $phpcsFile->getTokens();
44 | $current = $stackPtr;
45 | $previousLine = $tokens[$stackPtr]['line'] - 1;
46 | $prevLineTokens = [];
47 |
48 | while ($current >= 0 && $tokens[$current]['line'] >= $previousLine) {
49 | if (
50 | $tokens[$current]['line'] == $previousLine
51 | && $tokens[$current]['code'] !== T_WHITESPACE
52 | && $tokens[$current]['code'] !== T_COMMENT
53 | && $tokens[$current]['code'] !== T_DOC_COMMENT_OPEN_TAG
54 | && $tokens[$current]['code'] !== T_DOC_COMMENT_TAG
55 | && $tokens[$current]['code'] !== T_DOC_COMMENT_STRING
56 | && $tokens[$current]['code'] !== T_DOC_COMMENT_CLOSE_TAG
57 | && $tokens[$current]['code'] !== T_DOC_COMMENT_WHITESPACE
58 | ) {
59 | $prevLineTokens[] = $tokens[$current]['code'];
60 | }
61 | $current--;
62 | }
63 |
64 | if (
65 | isset($prevLineTokens[0])
66 | && ($prevLineTokens[0] === T_OPEN_CURLY_BRACKET
67 | || $prevLineTokens[0] === T_COLON
68 | || $prevLineTokens[0] === T_OPEN_TAG)
69 | ) {
70 | return;
71 | } elseif (count($prevLineTokens) > 0) {
72 | $fix = $phpcsFile->addFixableError(
73 | 'Missing blank line before return statement',
74 | $stackPtr,
75 | 'BlankLineBeforeReturn',
76 | );
77 | if ($fix === true) {
78 | $phpcsFile->fixer->beginChangeset();
79 | $phpcsFile->fixer->addNewlineBefore($stackPtr - 1);
80 | $phpcsFile->fixer->endChangeset();
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php:
--------------------------------------------------------------------------------
1 | getDeclarationName($stackPtr);
66 | if ($methodName === null) {
67 | return;
68 | }
69 |
70 | $className = $phpcsFile->getDeclarationName($currScope);
71 | $errorData = [$className . '::' . $methodName];
72 |
73 | // Ignore magic methods
74 | if (preg_match('/^__(' . implode('|', $this->_magicMethods) . ')$/', $methodName)) {
75 | return;
76 | }
77 |
78 | $methodProps = $phpcsFile->getMethodProperties($stackPtr);
79 | if ($methodProps['scope_specified'] === false) {
80 | // Let another sniffer take care of that
81 | return;
82 | }
83 |
84 | $isPublic = $methodProps['scope'] === 'public';
85 |
86 | if ($isPublic === true && $methodName[0] === '_') {
87 | $error = 'Public method name "%s" must not be prefixed with underscore';
88 | $phpcsFile->addError($error, $stackPtr, 'PublicWithUnderscore', $errorData);
89 |
90 | return;
91 | }
92 | }
93 |
94 | /**
95 | * @inheritDoc
96 | */
97 | protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
98 | {
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/NamingConventions/ValidTraitNameSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
40 | $traitName = $tokens[$stackPtr + 2]['content'];
41 |
42 | if (substr($traitName, -5) !== 'Trait') {
43 | $error = 'Traits must have a "Trait" suffix.';
44 | $phpcsFile->addError($error, $stackPtr, 'InvalidTraitName');
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/PHP/DisallowShortOpenTagSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
51 | $openTag = $tokens[$stackPtr];
52 |
53 | if (trim($openTag['content']) === '') {
54 | $error = 'Short PHP opening tag used; expected "addError($error, $stackPtr, 'Found', $data);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/PHP/SingleQuoteSniff.php:
--------------------------------------------------------------------------------
1 |
7 | * Dariusz Rumiński
8 | *
9 | * This source file is subject to the MIT license that is bundled
10 | * with this source code in the file LICENSE.
11 | */
12 |
13 | namespace CakePHP\Sniffs\PHP;
14 |
15 | use PHP_CodeSniffer\Files\File;
16 | use PHP_CodeSniffer\Sniffs\Sniff;
17 | use PHP_CodeSniffer\Util\Tokens;
18 |
19 | /**
20 | * Converts double quotes to single quotes for simple strings.
21 | *
22 | * @author Gregor Harlan
23 | * @author Mark Scherer
24 | */
25 | class SingleQuoteSniff implements Sniff
26 | {
27 | /**
28 | * @inheritDoc
29 | */
30 | public function register()
31 | {
32 | return [T_CONSTANT_ENCAPSED_STRING];
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function process(File $phpcsFile, $stackPtr)
39 | {
40 | $tokens = $phpcsFile->getTokens();
41 |
42 | // Skip for complex multiline
43 | $prevIndex = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);
44 | if ($prevIndex && $tokens[$prevIndex]['code'] === T_CONSTANT_ENCAPSED_STRING) {
45 | return;
46 | }
47 |
48 | $content = $tokens[$stackPtr]['content'];
49 | if (
50 | $content[0] === '"'
51 | && strpos($content, "'") === false
52 | && strpos($content, "\n") === false
53 | // regex: odd number of backslashes, not followed by double quote or dollar
54 | && !preg_match('/(?addFixableError(
57 | 'Use single instead of double quotes for simple strings.',
58 | $stackPtr,
59 | 'UseSingleQuote',
60 | );
61 | if ($fix) {
62 | $content = substr($content, 1, -1);
63 | $content = str_replace(['\\"', '\\$'], ['"', '$'], $content);
64 | $phpcsFile->fixer->replaceToken($stackPtr, '\'' . $content . '\'');
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/EmptyLinesSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
44 | // If the current and next two tokens are newlines
45 | // We can remove the next token (the first newline)
46 | if (
47 | $tokens[$stackPtr]['content'] === $phpcsFile->eolChar
48 | && isset($tokens[$stackPtr + 1])
49 | && $tokens[$stackPtr + 1]['content'] === $phpcsFile->eolChar
50 | && isset($tokens[$stackPtr + 2])
51 | && $tokens[$stackPtr + 2]['content'] === $phpcsFile->eolChar
52 | ) {
53 | $error = 'Found more than a single empty line between content';
54 | $fix = $phpcsFile->addFixableError($error, $stackPtr + 2, 'EmptyLines');
55 | if ($fix) {
56 | $phpcsFile->fixer->replaceToken($stackPtr + 2, '');
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/FunctionCallSpacingSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
49 |
50 | // Find the next non-empty token.
51 | $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
52 |
53 | if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) {
54 | // Not a function call.
55 | return;
56 | }
57 |
58 | // Look for funcName (
59 | if ($stackPtr + 1 !== $openBracket) {
60 | $error = 'Space before opening parenthesis of function call not allowed';
61 | $phpcsFile->addError($error, $stackPtr, 'SpaceBeforeOpenBracket');
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/FunctionClosingBraceSpaceSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
39 |
40 | if (isset($tokens[$stackPtr]['scope_closer']) === false) {
41 | // Probably an interface method.
42 | return;
43 | }
44 |
45 | $closeBrace = $tokens[$stackPtr]['scope_closer'];
46 | $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, $closeBrace - 1, null, true);
47 |
48 | $braceLine = $tokens[$closeBrace]['line'];
49 | $prevLine = $tokens[$prevContent]['line'];
50 |
51 | $found = $braceLine - $prevLine - 1;
52 | if (
53 | $phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
54 | || isset($tokens[$stackPtr]['nested_parenthesis']) === true
55 | ) {
56 | // Nested function.
57 | if ($found < 0) {
58 | $error = 'Closing brace of nested function must be on a new line';
59 | $phpcsFile->addError($error, $closeBrace, 'ContentBeforeClose');
60 | } elseif ($found > 0) {
61 | $error = 'Expected 0 blank lines before closing brace of nested function; %s found';
62 | $data = [$found];
63 | $phpcsFile->addError($error, $closeBrace, 'SpacingBeforeNestedClose', $data);
64 | }
65 | } else {
66 | if ($found !== 0) {
67 | $error = 'Expected 0 blank lines before closing function brace; %s found';
68 | $data = [$found];
69 | $phpcsFile->addError($error, $closeBrace, 'SpacingBeforeClose', $data);
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/FunctionOpeningBraceSpaceSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
39 |
40 | if (isset($tokens[$stackPtr]['scope_opener']) === false) {
41 | // Probably an interface method.
42 | return;
43 | }
44 |
45 | $openBrace = $tokens[$stackPtr]['scope_opener'];
46 | $nextContent = $phpcsFile->findNext(T_WHITESPACE, $openBrace + 1, null, true);
47 |
48 | if ($nextContent === $tokens[$stackPtr]['scope_closer']) {
49 | // The next bit of content is the closing brace, so this
50 | // is an empty function and should have a blank line
51 | // between the opening and closing braces.
52 | return;
53 | }
54 |
55 | $braceLine = $tokens[$openBrace]['line'];
56 | $nextLine = $tokens[$nextContent]['line'];
57 |
58 | $found = $nextLine - $braceLine - 1;
59 | if ($found > 0) {
60 | $error = 'Expected 0 blank lines after opening function brace; %s found';
61 | $data = [$found];
62 | $phpcsFile->addError($error, $openBrace, 'SpacingAfter', $data);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
43 |
44 | $level = $tokens[$stackPointer]['level'];
45 | if ($level < 1) {
46 | return;
47 | }
48 |
49 | $openingBraceIndex = $phpCsFile->findNext(T_OPEN_CURLY_BRACKET, $stackPointer + 1);
50 | // Fix interface methods
51 | if (!$openingBraceIndex) {
52 | $openingParenthesisIndex = $phpCsFile->findNext(T_OPEN_PARENTHESIS, $stackPointer + 1);
53 | $closingParenthesisIndex = $tokens[$openingParenthesisIndex]['parenthesis_closer'];
54 |
55 | $semicolonIndex = $phpCsFile->findNext(T_SEMICOLON, $closingParenthesisIndex + 1);
56 | if (!$semicolonIndex) {
57 | return;
58 | }
59 |
60 | $nextContentIndex = $phpCsFile->findNext(T_WHITESPACE, $semicolonIndex + 1, null, true);
61 |
62 | // Do not mess with the end of the class
63 | if ($tokens[$nextContentIndex]['code'] === T_CLOSE_CURLY_BRACKET) {
64 | return;
65 | }
66 |
67 | if ($tokens[$nextContentIndex]['line'] - $tokens[$semicolonIndex]['line'] <= 1) {
68 | $fix = $phpCsFile->addFixableError(
69 | 'Every function/method needs a newline afterwards',
70 | $closingParenthesisIndex,
71 | 'Abstract',
72 | );
73 | if ($fix) {
74 | $phpCsFile->fixer->addNewline($semicolonIndex);
75 | }
76 | }
77 |
78 | return;
79 | }
80 |
81 | $closingBraceIndex = $tokens[$openingBraceIndex]['scope_closer'];
82 |
83 | // Ignore closures
84 | $nextIndex = $phpCsFile->findNext(Tokens::$emptyTokens, $closingBraceIndex + 1, null, true);
85 | if (in_array($tokens[$nextIndex]['content'], [';', ',', ')'])) {
86 | return;
87 | }
88 |
89 | $nextContentIndex = $phpCsFile->findNext(T_WHITESPACE, $closingBraceIndex + 1, null, true);
90 | if (!$nextContentIndex) {
91 | return;
92 | }
93 |
94 | // Do not mess with the end of the class
95 | if ($tokens[$nextContentIndex]['code'] === T_CLOSE_CURLY_BRACKET) {
96 | return;
97 | }
98 |
99 | $this->assertNewLineAtTheEnd($phpCsFile, $closingBraceIndex, $nextContentIndex);
100 | $this->assertNewLineAtTheBeginning($phpCsFile, $stackPointer);
101 | }
102 |
103 | /**
104 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
105 | * @param int $closingBraceIndex Index
106 | * @param int|null $nextContentIndex Index
107 | * @return void
108 | */
109 | protected function assertNewLineAtTheEnd(File $phpCsFile, int $closingBraceIndex, ?int $nextContentIndex): void
110 | {
111 | $tokens = $phpCsFile->getTokens();
112 |
113 | if (!$nextContentIndex || $tokens[$nextContentIndex]['line'] - $tokens[$closingBraceIndex]['line'] <= 1) {
114 | $fix = $phpCsFile->addFixableError(
115 | 'Every function/method needs a newline afterwards',
116 | $closingBraceIndex,
117 | 'Concrete',
118 | );
119 | if ($fix) {
120 | $phpCsFile->fixer->addNewline($closingBraceIndex);
121 | }
122 | }
123 | }
124 |
125 | /**
126 | * Asserts newline at the beginning, including the doc block.
127 | *
128 | * @param \PHP_CodeSniffer\Files\File $phpCsFile File
129 | * @param int $stackPointer Stack pointer
130 | * @return void
131 | */
132 | protected function assertNewLineAtTheBeginning(File $phpCsFile, int $stackPointer): void
133 | {
134 | $tokens = $phpCsFile->getTokens();
135 |
136 | $line = $tokens[$stackPointer]['line'];
137 | $firstTokenInLineIndex = $stackPointer;
138 | while ($tokens[$firstTokenInLineIndex - 1]['line'] === $line) {
139 | $firstTokenInLineIndex--;
140 | }
141 |
142 | $prevContentIndex = $phpCsFile->findPrevious(T_WHITESPACE, $firstTokenInLineIndex - 1, null, true);
143 |
144 | if ($tokens[$prevContentIndex]['code'] === T_ATTRIBUTE_END) {
145 | return;
146 | }
147 |
148 | if ($tokens[$prevContentIndex]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
149 | $firstTokenInLineIndex = $tokens[$prevContentIndex]['comment_opener'];
150 | while ($tokens[$firstTokenInLineIndex - 1]['line'] === $line) {
151 | $firstTokenInLineIndex--;
152 | }
153 | }
154 |
155 | $prevContentIndex = $phpCsFile->findPrevious(T_WHITESPACE, $firstTokenInLineIndex - 1, null, true);
156 | if (!$prevContentIndex) {
157 | return;
158 | }
159 |
160 | // Do not mess with the start of the class
161 | if ($tokens[$prevContentIndex]['code'] === T_OPEN_CURLY_BRACKET) {
162 | return;
163 | }
164 |
165 | if ($tokens[$prevContentIndex]['line'] < $tokens[$firstTokenInLineIndex]['line'] - 1) {
166 | return;
167 | }
168 |
169 | $fix = $phpCsFile->addFixableError(
170 | 'Every function/method needs a newline before',
171 | $firstTokenInLineIndex,
172 | 'Concrete',
173 | );
174 | if ($fix) {
175 | $phpCsFile->fixer->addNewline($prevContentIndex);
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/CakePHP/Sniffs/WhiteSpace/TabAndSpaceSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
41 |
42 | $line = $tokens[$stackPtr]['line'];
43 | if ($stackPtr > 0 && $tokens[$stackPtr - 1]['line'] !== $line) {
44 | return;
45 | }
46 |
47 | if (strpos($tokens[$stackPtr]['content'], ' ') !== false) {
48 | $error = 'Double space found';
49 | $phpcsFile->addError($error, $stackPtr, 'DoubleSpace');
50 | }
51 | if (strpos($tokens[$stackPtr]['content'], " \t") !== false) {
52 | $error = 'Space and tab found';
53 | $phpcsFile->addError($error, $stackPtr, 'SpaceAndTab');
54 | }
55 | if (strpos($tokens[$stackPtr]['content'], "\t ") !== false) {
56 | $error = 'Tab and space found';
57 | $phpcsFile->addError($error, $stackPtr, 'TabAndSpace');
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/CakePHP/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | CakePHP coding standard
4 |
5 |
6 |
7 | \.git
8 | /*/tmp/
9 | tests/*/templates/*
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | */config/Migrations/*
26 | */config/Seeds/*
27 |
28 |
29 | */config/*
30 | */tests/*
31 |
32 |
33 | */src/Controller/*
34 | */src/Command/*
35 | */src/Shell/*
36 | */tests/*
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | */tests/*
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | */tests/*
67 |
68 |
69 | */tests/*
70 |
71 |
72 |
73 |
74 |
75 |
76 | */config/Migrations/*
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | */tests/*
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | */templates/*
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | */config/*
192 | */templates/*
193 | */tests/Fixture/*
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | */tests/*
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | */tests/*
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | */tests/*
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | */tests/*
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2005-present, Cake Software Foundation, Inc. (https://cakefoundation.org)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CakePHP Code Sniffer
2 |
3 | 
4 | [](https://packagist.org/packages/cakephp/cakephp-codesniffer)
5 | [](https://packagist.org/packages/cakephp/cakephp-codesniffer)
6 | [](LICENSE)
7 |
8 | This code works with [squizlabs/php_codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer)
9 | and checks code against the coding standards used in CakePHP.
10 |
11 | This sniffer package follows [PSR-12](https://www.php-fig.org/psr/psr-12/) completely and ships with a lot of additional fixers on top.
12 |
13 | [List of included sniffs](/docs)
14 |
15 | ## Which version should I use?
16 | See [version map](https://github.com/cakephp/cakephp-codesniffer/wiki).
17 |
18 | ## Installation
19 |
20 | You should install this codesniffer with composer:
21 |
22 | composer require --dev cakephp/cakephp-codesniffer
23 | vendor/bin/phpcs --config-set installed_paths /path/to/your/app/vendor/cakephp/cakephp-codesniffer
24 |
25 | The second command lets `phpcs` know where to find your new sniffs. Ensure that
26 | you do not overwrite any existing `installed_paths` value. Alternatively, install
27 | the [`dealerdirect/phpcodesniffer-composer-installer`](https://github.com/Dealerdirect/phpcodesniffer-composer-installer)
28 | composer package which will handle configuring the `phpcs` `installed_paths` for you.
29 |
30 | ## Usage
31 |
32 | :warning: Warning when these sniffs are installed with composer, ensure that
33 | you have configured the CodeSniffer `installed_paths` setting.
34 |
35 | Depending on how you installed the code sniffer changes how you run it. If you have
36 | installed phpcs, and this package with PEAR, you can do the following:
37 |
38 | vendor/bin/phpcs --colors -p -s --standard=CakePHP /path/to/code/
39 |
40 | You can also copy the `phpcs.xml.dist` file to your project's root folder as `phpcs.xml`.
41 | This file will import the CakePHP Coding Standard. From there you can edit it to
42 | include/exclude as needed. With this file in place, you can run:
43 |
44 | vendor/bin/phpcs --colors -p -s /path/to/code/
45 |
46 | If you are using Composer to manage your CakePHP project, you can also add the below to your composer.json file:
47 |
48 | ```json
49 | {
50 | "scripts": {
51 | "cs-check": "vendor/bin/phpcs --colors -p -s --extensions=php src/ tests/"
52 | }
53 | }
54 | ```
55 |
56 | ## Running Tests
57 |
58 | You can run tests with composer. Because of how PHPCS test suites work, there is
59 | additional configuration state in `phpcs` that is required.
60 |
61 | ```bash
62 | composer test
63 | ```
64 |
65 | Once this has been done once, you can use `composer phpunit` to run the
66 | tests for the rules in this repository.
67 |
68 | The tests are present inside the `CakePHP/Tests/` folder.
69 |
70 | ## Contributing
71 |
72 | If you'd like to contribute to the Code Sniffer, you can fork the project add
73 | features and send pull requests.
74 |
75 | > [!NOTE]
76 | > Please make sure to run `composer docs` if you change the ruleset.xml file.
77 |
78 | ## Releasing CakePHP Code Sniffer
79 |
80 | * Create a signed tag
81 | * Write the changelog in the tag commit
82 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cakephp/cakephp-codesniffer",
3 | "description": "CakePHP CodeSniffer Standards",
4 | "type": "phpcodesniffer-standard",
5 | "keywords": ["framework", "codesniffer"],
6 | "homepage": "https://cakephp.org",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "CakePHP Community",
11 | "homepage": "https://github.com/cakephp/cakephp-codesniffer/graphs/contributors"
12 | }
13 | ],
14 | "support": {
15 | "issues": "https://github.com/cakephp/cakephp-codesniffer/issues",
16 | "forum": "https://stackoverflow.com/tags/cakephp",
17 | "irc": "irc://irc.freenode.org/cakephp",
18 | "source": "https://github.com/cakephp/cakephp-codesniffer"
19 | },
20 | "require": {
21 | "php": ">=8.1.0",
22 | "phpstan/phpdoc-parser": "^2.1.0",
23 | "slevomat/coding-standard": "^8.16",
24 | "squizlabs/php_codesniffer": "^3.9"
25 | },
26 | "require-dev": {
27 | "phpunit/phpunit": "^9.3.4"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "CakePHP\\": "CakePHP/"
32 | }
33 | },
34 | "config": {
35 | "allow-plugins": {
36 | "dealerdirect/phpcodesniffer-composer-installer": true
37 | }
38 | },
39 | "scripts": {
40 | "add-standard" : "phpcs --config-set installed_paths $(pwd)",
41 | "phpunit": "phpunit vendor/squizlabs/php_codesniffer/tests/AllTests.php --filter CakePHP --no-configuration --bootstrap=vendor/squizlabs/php_codesniffer/tests/bootstrap.php --dont-report-useless-tests",
42 | "test": [
43 | "@add-standard",
44 | "@phpunit"
45 | ],
46 | "cs-check": "phpcs --colors --parallel=16 -p -s CakePHP/",
47 | "cs-fix": "phpcbf --colors --parallel=16 -p CakePHP/",
48 | "stan": "tools/phpstan analyse",
49 | "docs": "php docs/generate.php",
50 | "explain": "phpcs -e --standard=CakePHP"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # CakePHP ruleset
2 |
3 | The CakePHP standard contains 155 sniffs
4 |
5 | CakePHP (19 sniffs)
6 | -------------------
7 | - CakePHP.Classes.ReturnTypeHint
8 | - CakePHP.Commenting.DocBlockAlignment
9 | - CakePHP.Commenting.FunctionComment
10 | - CakePHP.Commenting.InheritDoc
11 | - CakePHP.Commenting.TypeHint
12 | - CakePHP.ControlStructures.ControlStructures
13 | - CakePHP.ControlStructures.ElseIfDeclaration
14 | - CakePHP.ControlStructures.WhileStructures
15 | - CakePHP.Formatting.BlankLineBeforeReturn
16 | - CakePHP.NamingConventions.ValidFunctionName
17 | - CakePHP.NamingConventions.ValidTraitName
18 | - CakePHP.PHP.DisallowShortOpenTag
19 | - CakePHP.PHP.SingleQuote
20 | - CakePHP.WhiteSpace.EmptyLines
21 | - CakePHP.WhiteSpace.FunctionCallSpacing
22 | - CakePHP.WhiteSpace.FunctionClosingBraceSpace
23 | - CakePHP.WhiteSpace.FunctionOpeningBraceSpace
24 | - CakePHP.WhiteSpace.FunctionSpacing
25 | - CakePHP.WhiteSpace.TabAndSpace
26 |
27 | Generic (26 sniffs)
28 | -------------------
29 | - Generic.Arrays.DisallowLongArraySyntax
30 | - Generic.CodeAnalysis.ForLoopShouldBeWhileLoop
31 | - Generic.CodeAnalysis.ForLoopWithTestFunctionCall
32 | - Generic.CodeAnalysis.JumbledIncrementer
33 | - Generic.CodeAnalysis.UnconditionalIfStatement
34 | - Generic.CodeAnalysis.UnnecessaryFinalModifier
35 | - Generic.ControlStructures.InlineControlStructure
36 | - Generic.Files.ByteOrderMark
37 | - Generic.Files.LineEndings
38 | - Generic.Files.LineLength
39 | - Generic.Formatting.DisallowMultipleStatements
40 | - Generic.Formatting.SpaceAfterCast
41 | - Generic.Functions.FunctionCallArgumentSpacing
42 | - Generic.NamingConventions.UpperCaseConstantName
43 | - Generic.PHP.DeprecatedFunctions
44 | - Generic.PHP.DisallowAlternativePHPTags
45 | - Generic.PHP.DisallowShortOpenTag
46 | - Generic.PHP.ForbiddenFunctions
47 | - Generic.PHP.LowerCaseConstant
48 | - Generic.PHP.LowerCaseKeyword
49 | - Generic.PHP.LowerCaseType
50 | - Generic.PHP.NoSilencedErrors
51 | - Generic.WhiteSpace.DisallowTabIndent
52 | - Generic.WhiteSpace.IncrementDecrementSpacing
53 | - Generic.WhiteSpace.LanguageConstructSpacing
54 | - Generic.WhiteSpace.ScopeIndent
55 |
56 | PEAR (1 sniff)
57 | --------------
58 | - PEAR.Functions.ValidDefaultValue
59 |
60 | PSR1 (3 sniffs)
61 | ---------------
62 | - PSR1.Classes.ClassDeclaration
63 | - PSR1.Files.SideEffects
64 | - PSR1.Methods.CamelCapsMethodName
65 |
66 | PSR2 (9 sniffs)
67 | ---------------
68 | - PSR2.Classes.ClassDeclaration
69 | - PSR2.Classes.PropertyDeclaration
70 | - PSR2.ControlStructures.ElseIfDeclaration
71 | - PSR2.ControlStructures.SwitchDeclaration
72 | - PSR2.Files.ClosingTag
73 | - PSR2.Files.EndFileNewline
74 | - PSR2.Methods.FunctionCallSignature
75 | - PSR2.Methods.FunctionClosingBrace
76 | - PSR2.Methods.MethodDeclaration
77 |
78 | PSR12 (17 sniffs)
79 | -----------------
80 | - PSR12.Classes.AnonClassDeclaration
81 | - PSR12.Classes.ClassInstantiation
82 | - PSR12.Classes.ClosingBrace
83 | - PSR12.Classes.OpeningBraceSpace
84 | - PSR12.ControlStructures.BooleanOperatorPlacement
85 | - PSR12.ControlStructures.ControlStructureSpacing
86 | - PSR12.Files.DeclareStatement
87 | - PSR12.Files.FileHeader
88 | - PSR12.Files.ImportStatement
89 | - PSR12.Files.OpenTag
90 | - PSR12.Functions.NullableTypeDeclaration
91 | - PSR12.Functions.ReturnTypeDeclaration
92 | - PSR12.Keywords.ShortFormTypeKeywords
93 | - PSR12.Namespaces.CompoundNamespaceDepth
94 | - PSR12.Operators.OperatorSpacing
95 | - PSR12.Properties.ConstantVisibility
96 | - PSR12.Traits.UseDeclaration
97 |
98 | SlevomatCodingStandard (52 sniffs)
99 | ----------------------------------
100 | - SlevomatCodingStandard.Arrays.TrailingArrayComma
101 | - SlevomatCodingStandard.Attributes.AttributeAndTargetSpacing
102 | - SlevomatCodingStandard.Attributes.RequireAttributeAfterDocComment
103 | - SlevomatCodingStandard.Classes.BackedEnumTypeSpacing
104 | - SlevomatCodingStandard.Classes.ClassConstantVisibility
105 | - SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces
106 | - SlevomatCodingStandard.Classes.ModernClassNameReference
107 | - SlevomatCodingStandard.Classes.PropertyDeclaration
108 | - SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment
109 | - SlevomatCodingStandard.Commenting.DocCommentSpacing
110 | - SlevomatCodingStandard.Commenting.EmptyComment
111 | - SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration
112 | - SlevomatCodingStandard.ControlStructures.AssignmentInCondition
113 | - SlevomatCodingStandard.ControlStructures.DisallowContinueWithoutIntegerOperandInSwitch
114 | - SlevomatCodingStandard.ControlStructures.DisallowYodaComparison
115 | - SlevomatCodingStandard.ControlStructures.LanguageConstructWithParentheses
116 | - SlevomatCodingStandard.ControlStructures.NewWithParentheses
117 | - SlevomatCodingStandard.ControlStructures.RequireNullCoalesceOperator
118 | - SlevomatCodingStandard.ControlStructures.RequireShortTernaryOperator
119 | - SlevomatCodingStandard.Exceptions.DeadCatch
120 | - SlevomatCodingStandard.Functions.ArrowFunctionDeclaration
121 | - SlevomatCodingStandard.Functions.DisallowTrailingCommaInCall
122 | - SlevomatCodingStandard.Functions.DisallowTrailingCommaInClosureUse
123 | - SlevomatCodingStandard.Functions.DisallowTrailingCommaInDeclaration
124 | - SlevomatCodingStandard.Functions.NamedArgumentSpacing
125 | - SlevomatCodingStandard.Functions.RequireTrailingCommaInCall
126 | - SlevomatCodingStandard.Functions.RequireTrailingCommaInClosureUse
127 | - SlevomatCodingStandard.Functions.RequireTrailingCommaInDeclaration
128 | - SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses
129 | - SlevomatCodingStandard.Namespaces.DisallowGroupUse
130 | - SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation
131 | - SlevomatCodingStandard.Namespaces.NamespaceDeclaration
132 | - SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
133 | - SlevomatCodingStandard.Namespaces.UnusedUses
134 | - SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash
135 | - SlevomatCodingStandard.Namespaces.UseFromSameNamespace
136 | - SlevomatCodingStandard.Namespaces.UseSpacing
137 | - SlevomatCodingStandard.PHP.ShortList
138 | - SlevomatCodingStandard.PHP.TypeCast
139 | - SlevomatCodingStandard.PHP.UselessParentheses
140 | - SlevomatCodingStandard.PHP.UselessSemicolon
141 | - SlevomatCodingStandard.TypeHints.DeclareStrictTypes
142 | - SlevomatCodingStandard.TypeHints.DNFTypeHintFormat
143 | - SlevomatCodingStandard.TypeHints.LongTypeHints
144 | - SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue
145 | - SlevomatCodingStandard.TypeHints.ParameterTypeHint
146 | - SlevomatCodingStandard.TypeHints.ParameterTypeHintSpacing
147 | - SlevomatCodingStandard.TypeHints.PropertyTypeHint
148 | - SlevomatCodingStandard.TypeHints.ReturnTypeHint
149 | - SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing
150 | - SlevomatCodingStandard.Variables.DuplicateAssignmentToVariable
151 | - SlevomatCodingStandard.Variables.UnusedVariable
152 |
153 | Squiz (27 sniffs)
154 | -----------------
155 | - Squiz.Arrays.ArrayBracketSpacing
156 | - Squiz.Classes.ClassFileName
157 | - Squiz.Classes.LowercaseClassKeywords
158 | - Squiz.Classes.ValidClassName
159 | - Squiz.Commenting.DocCommentAlignment
160 | - Squiz.ControlStructures.ControlSignature
161 | - Squiz.ControlStructures.ForEachLoopDeclaration
162 | - Squiz.ControlStructures.ForLoopDeclaration
163 | - Squiz.ControlStructures.LowercaseDeclaration
164 | - Squiz.Functions.FunctionDeclaration
165 | - Squiz.Functions.FunctionDeclarationArgumentSpacing
166 | - Squiz.Functions.LowercaseFunctionKeywords
167 | - Squiz.Functions.MultiLineFunctionDeclaration
168 | - Squiz.Operators.ValidLogicalOperators
169 | - Squiz.PHP.DisallowSizeFunctionsInLoops
170 | - Squiz.PHP.Eval
171 | - Squiz.PHP.NonExecutableCode
172 | - Squiz.Scope.MemberVarScope
173 | - Squiz.Scope.MethodScope
174 | - Squiz.Scope.StaticThisUsage
175 | - Squiz.WhiteSpace.CastSpacing
176 | - Squiz.WhiteSpace.ControlStructureSpacing
177 | - Squiz.WhiteSpace.LogicalOperatorSpacing
178 | - Squiz.WhiteSpace.ScopeClosingBrace
179 | - Squiz.WhiteSpace.ScopeKeywordSpacing
180 | - Squiz.WhiteSpace.SemicolonSpacing
181 | - Squiz.WhiteSpace.SuperfluousWhitespace
182 |
183 | Zend (1 sniff)
184 | --------------
185 | - Zend.NamingConventions.ValidVariableName
--------------------------------------------------------------------------------
/docs/generate.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |