1<?php
2/**
3 * Checks the cyclomatic complexity (McCabe) for functions.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16/**
17 * Checks the cyclomatic complexity (McCabe) for functions.
18 *
19 * The cyclomatic complexity (also called McCabe code metrics)
20 * indicates the complexity within a function by counting
21 * the different paths the function includes.
22 *
23 * @category  PHP
24 * @package   PHP_CodeSniffer
25 * @author    Johann-Peter Hartmann <hartmann@mayflower.de>
26 * @author    Greg Sherwood <gsherwood@squiz.net>
27 * @copyright 2007-2014 Mayflower GmbH
28 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 * @version   Release: @package_version@
30 * @link      http://pear.php.net/package/PHP_CodeSniffer
31 */
32class Generic_Sniffs_Metrics_CyclomaticComplexitySniff implements PHP_CodeSniffer_Sniff
33{
34
35    /**
36     * A complexity higher than this value will throw a warning.
37     *
38     * @var int
39     */
40    public $complexity = 10;
41
42    /**
43     * A complexity higher than this value will throw an error.
44     *
45     * @var int
46     */
47    public $absoluteComplexity = 20;
48
49
50    /**
51     * Returns an array of tokens this test wants to listen for.
52     *
53     * @return array
54     */
55    public function register()
56    {
57        return array(T_FUNCTION);
58
59    }//end register()
60
61
62    /**
63     * Processes this test, when one of its tokens is encountered.
64     *
65     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
66     * @param int                  $stackPtr  The position of the current token
67     *                                        in the stack passed in $tokens.
68     *
69     * @return void
70     */
71    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
72    {
73        $this->currentFile = $phpcsFile;
74
75        $tokens = $phpcsFile->getTokens();
76
77        // Ignore abstract methods.
78        if (isset($tokens[$stackPtr]['scope_opener']) === false) {
79            return;
80        }
81
82        // Detect start and end of this function definition.
83        $start = $tokens[$stackPtr]['scope_opener'];
84        $end   = $tokens[$stackPtr]['scope_closer'];
85
86        // Predicate nodes for PHP.
87        $find = array(
88                 T_CASE    => true,
89                 T_DEFAULT => true,
90                 T_CATCH   => true,
91                 T_IF      => true,
92                 T_FOR     => true,
93                 T_FOREACH => true,
94                 T_WHILE   => true,
95                 T_DO      => true,
96                 T_ELSEIF  => true,
97                );
98
99        $complexity = 1;
100
101        // Iterate from start to end and count predicate nodes.
102        for ($i = ($start + 1); $i < $end; $i++) {
103            if (isset($find[$tokens[$i]['code']]) === true) {
104                $complexity++;
105            }
106        }
107
108        if ($complexity > $this->absoluteComplexity) {
109            $error = 'Function\'s cyclomatic complexity (%s) exceeds allowed maximum of %s';
110            $data  = array(
111                      $complexity,
112                      $this->absoluteComplexity,
113                     );
114            $phpcsFile->addError($error, $stackPtr, 'MaxExceeded', $data);
115        } else if ($complexity > $this->complexity) {
116            $warning = 'Function\'s cyclomatic complexity (%s) exceeds %s; consider refactoring the function';
117            $data    = array(
118                        $complexity,
119                        $this->complexity,
120                       );
121            $phpcsFile->addWarning($warning, $stackPtr, 'TooHigh', $data);
122        }
123
124    }//end process()
125
126
127}//end class
128