@@ -34,6 +34,16 @@ final class FunctionLengthSniff implements Sniff
3434 */
3535 public $ ignoreDocBlocks = true ;
3636
37+ /**
38+ * @var true
39+ */
40+ public $ ignoreSingleLineComments = true ;
41+
42+ /**
43+ * @var true
44+ */
45+ public $ ignoreWhiteLines = true ;
46+
3747 /**
3848 * @return int[]
3949 */
@@ -49,16 +59,30 @@ public function register(): array
4959 public function process (File $ file , $ position )
5060 {
5161 $ length = $ this ->getStructureLengthInLines ($ file , $ position );
62+ if ($ length <= $ this ->maxLength ) {
63+ return ;
64+ }
5265
53- if ($ length > $ this ->maxLength ) {
54- $ error = sprintf (
55- 'Your function is too long. Currently using %d lines. Can be up to %d lines. ' ,
56- $ length ,
57- $ this ->maxLength
58- );
59-
60- $ file ->addError ($ error , $ position , 'TooLong ' );
66+ $ ignored = [];
67+ $ suffix = '' ;
68+ $ this ->ignoreWhiteLines and $ ignored [] = 'white lines ' ;
69+ $ this ->ignoreSingleLineComments and $ ignored [] = 'single line comments ' ;
70+ $ this ->ignoreDocBlocks and $ ignored [] = 'doc blocks ' ;
71+ if ($ ignored ) {
72+ $ suffix = ' (ignoring ' ;
73+ $ last = array_pop ($ ignored );
74+ $ others = implode (', ' , $ ignored );
75+ $ suffix .= $ others ? "{$ others } and {$ last }) " : "{$ last }) " ;
6176 }
77+
78+ $ error = sprintf (
79+ 'Your function is too long. Currently using %d lines%s, max is %d. ' ,
80+ $ length ,
81+ $ suffix ,
82+ $ this ->maxLength
83+ );
84+
85+ $ file ->addError ($ error , $ position , 'TooLong ' );
6286 }
6387
6488 /**
@@ -77,25 +101,100 @@ public function getStructureLengthInLines(File $file, int $position): int
77101 return 0 ;
78102 }
79103
80- $ opener = $ token ['scope_opener ' ];
81- $ closer = $ token ['scope_closer ' ];
82- $ length = $ tokens [$ closer ]['line ' ] - $ tokens [$ opener ]['line ' ];
104+ $ start = $ token ['scope_opener ' ];
105+ $ end = $ token ['scope_closer ' ];
106+ $ length = $ tokens [$ end ]['line ' ] - $ tokens [$ start ]['line ' ];
83107
84- if (! $ this ->ignoreDocBlocks ) {
108+ if ($ length < $ this ->maxLength ) {
85109 return $ length ;
86110 }
87111
88- $ decrease = 0 ;
89- for ($ i = $ opener + 1 ; $ i < $ closer ; $ i ++) {
90- if ($ tokens [$ i ]['code ' ] === T_DOC_COMMENT_OPEN_TAG ) {
91- $ openerLine = (int )$ tokens [$ i ]['line ' ];
92- $ closer = $ tokens [$ i ]['comment_closer ' ] ?? null ;
93- $ decrease += is_numeric ($ closer )
94- ? (int )$ tokens [$ closer ]['line ' ] - ($ openerLine - 1 )
95- : 1 ;
112+ return $ length - $ this ->collectLinesToExclude ($ start , $ end , $ tokens );
113+ }
114+
115+ /**
116+ * @param int $start
117+ * @param int $end
118+ * @param array $tokens
119+ * @return int
120+ */
121+ private function collectLinesToExclude (
122+ int $ start ,
123+ int $ end ,
124+ array $ tokens
125+ ): int {
126+
127+ $ linesData = $ docblocks = [];
128+
129+ $ skipLines = [$ tokens [$ start + 1 ]['line ' ], $ tokens [$ end - 1 ]['line ' ]];
130+ for ($ i = $ start + 1 ; $ i < $ end - 1 ; $ i ++) {
131+ if (in_array ($ tokens [$ i ]['line ' ], $ skipLines , true )) {
132+ continue ;
96133 }
134+
135+ $ docblocks = $ this ->docBlocksData ($ tokens , $ i , $ docblocks );
136+ $ linesData = $ this ->ignoredLinesData ($ tokens [$ i ], $ linesData );
137+ }
138+
139+ $ empty = array_filter (array_column ($ linesData , 'empty ' ));
140+ $ onlyComment = array_filter (array_column ($ linesData , 'only-comment ' ));
141+
142+ $ toExcludeCount = array_sum ($ docblocks );
143+ if ($ this ->ignoreWhiteLines ) {
144+ $ toExcludeCount += count ($ empty );
145+ }
146+ if ($ this ->ignoreSingleLineComments ) {
147+ $ toExcludeCount += count ($ onlyComment ) - count ($ empty );
148+ }
149+
150+ return $ toExcludeCount ;
151+ }
152+
153+ /**
154+ * @param array $token
155+ * @param array $lines
156+ * @return array
157+ */
158+ private function ignoredLinesData (array $ token , array $ lines ): array
159+ {
160+ $ line = $ token ['line ' ];
161+ if (!array_key_exists ($ line , $ lines )) {
162+ $ lines [$ line ] = ['empty ' => true , 'only-comment ' => true ];
163+ }
164+
165+ if (!in_array ($ token ['code ' ], [T_COMMENT , T_WHITESPACE ], true )) {
166+ $ lines [$ line ]['only-comment ' ] = false ;
97167 }
98168
99- return max (0 , $ length - $ decrease );
169+ if ($ token ['code ' ] !== T_WHITESPACE ) {
170+ $ lines [$ line ]['empty ' ] = false ;
171+ }
172+
173+ return $ lines ;
174+ }
175+
176+ /**
177+ * @param array $tokens
178+ * @param int $position
179+ * @param array $docBlocks
180+ * @return array
181+ */
182+ private function docBlocksData (
183+ array $ tokens ,
184+ int $ position ,
185+ array $ docBlocks
186+ ): array {
187+ if (!$ this ->ignoreDocBlocks
188+ || $ tokens [$ position ]['code ' ] !== T_DOC_COMMENT_OPEN_TAG
189+ ) {
190+ return $ docBlocks ;
191+ }
192+
193+ $ closer = $ tokens [$ position ]['comment_closer ' ] ?? null ;
194+ $ docBlocks [] = is_numeric ($ closer )
195+ ? 1 + ($ tokens [$ closer ]['line ' ] - $ tokens [$ position ]['line ' ])
196+ : 1 ;
197+
198+ return $ docBlocks ;
100199 }
101200}
0 commit comments