1<?php
2/**
3 * Generic_Sniffs_Strings_UnnecessaryStringConcatSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
11 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 * @link      http://pear.php.net/package/PHP_CodeSniffer
13 */
14
15/**
16 * Generic_Sniffs_Strings_UnnecessaryStringConcatSniff.
17 *
18 * Checks that two strings are not concatenated together; suggests
19 * using one string instead.
20 *
21 * @category  PHP
22 * @package   PHP_CodeSniffer
23 * @author    Greg Sherwood <gsherwood@squiz.net>
24 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
25 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
26 * @version   Release: @package_version@
27 * @link      http://pear.php.net/package/PHP_CodeSniffer
28 */
29class Generic_Sniffs_Strings_UnnecessaryStringConcatSniff implements PHP_CodeSniffer_Sniff
30{
31
32    /**
33     * A list of tokenizers this sniff supports.
34     *
35     * @var array
36     */
37    public $supportedTokenizers = array(
38                                   'PHP',
39                                   'JS',
40                                  );
41
42    /**
43     * If true, an error will be thrown; otherwise a warning.
44     *
45     * @var bool
46     */
47    public $error = true;
48
49    /**
50     * If true, strings concatenated over multiple lines are allowed.
51     *
52     * Useful if you break strings over multiple lines to work
53     * within a max line length.
54     *
55     * @var bool
56     */
57    public $allowMultiline = false;
58
59
60    /**
61     * Returns an array of tokens this test wants to listen for.
62     *
63     * @return array
64     */
65    public function register()
66    {
67        return array(
68                T_STRING_CONCAT,
69                T_PLUS,
70               );
71
72    }//end register()
73
74
75    /**
76     * Processes this sniff, when one of its tokens is encountered.
77     *
78     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
79     * @param int                  $stackPtr  The position of the current token
80     *                                        in the stack passed in $tokens.
81     *
82     * @return void
83     */
84    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
85    {
86        // Work out which type of file this is for.
87        $tokens = $phpcsFile->getTokens();
88        if ($tokens[$stackPtr]['code'] === T_STRING_CONCAT) {
89            if ($phpcsFile->tokenizerType === 'JS') {
90                return;
91            }
92        } else {
93            if ($phpcsFile->tokenizerType === 'PHP') {
94                return;
95            }
96        }
97
98        $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
99        $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
100        if ($prev === false || $next === false) {
101            return;
102        }
103
104        if (isset(PHP_CodeSniffer_Tokens::$stringTokens[$tokens[$prev]['code']]) === true
105            && isset(PHP_CodeSniffer_Tokens::$stringTokens[$tokens[$next]['code']]) === true
106        ) {
107            if ($tokens[$prev]['content'][0] === $tokens[$next]['content'][0]) {
108                // Before we throw an error for PHP, allow strings to be
109                // combined if they would have < and ? next to each other because
110                // this trick is sometimes required in PHP strings.
111                if ($phpcsFile->tokenizerType === 'PHP') {
112                    $prevChar = substr($tokens[$prev]['content'], -2, 1);
113                    $nextChar = $tokens[$next]['content'][1];
114                    $combined = $prevChar.$nextChar;
115                    if ($combined === '?'.'>' || $combined === '<'.'?') {
116                        return;
117                    }
118                }
119
120                if ($this->allowMultiline === true
121                    && $tokens[$prev]['line'] !== $tokens[$next]['line']
122                ) {
123                    return;
124                }
125
126                $error = 'String concat is not required here; use a single string instead';
127                if ($this->error === true) {
128                    $phpcsFile->addError($error, $stackPtr, 'Found');
129                } else {
130                    $phpcsFile->addWarning($error, $stackPtr, 'Found');
131                }
132            }//end if
133        }//end if
134
135    }//end process()
136
137
138}//end class
139