Skip to content

Commit e72a83a

Browse files
authored
Merge pull request #60 from PHPCSStandards/feature/new-textstrings-class
New Utils\TextStrings class
2 parents e951d54 + d723d2e commit e72a83a

File tree

5 files changed

+464
-0
lines changed

5 files changed

+464
-0
lines changed

PHPCSUtils/Tokens/Collections.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,18 @@ class Collections
331331
\T_OPEN_SQUARE_BRACKET => \T_OPEN_SQUARE_BRACKET,
332332
\T_CLOSE_SQUARE_BRACKET => \T_CLOSE_SQUARE_BRACKET,
333333
];
334+
335+
/**
336+
* Tokens which can start a - potentially multi-line - text string.
337+
*
338+
* @since 1.0.0
339+
*
340+
* @var array <int|string> => <int|string>
341+
*/
342+
public static $textStingStartTokens = [
343+
\T_START_HEREDOC => \T_START_HEREDOC,
344+
\T_START_NOWDOC => \T_START_NOWDOC,
345+
\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
346+
\T_DOUBLE_QUOTED_STRING => \T_DOUBLE_QUOTED_STRING,
347+
];
334348
}

PHPCSUtils/Utils/TextStrings.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Utils;
12+
13+
use PHP_CodeSniffer\Exceptions\RuntimeException;
14+
use PHP_CodeSniffer\Files\File;
15+
use PHPCSUtils\Tokens\Collections;
16+
17+
/**
18+
* Utility functions for working with text string tokens.
19+
*
20+
* @since 1.0.0
21+
*/
22+
class TextStrings
23+
{
24+
25+
/**
26+
* Get the complete contents of a - potentially multi-line - text string.
27+
*
28+
* @since 1.0.0
29+
*
30+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
31+
* @param int $stackPtr Pointer to the first text string token
32+
* of a multi-line text string or to a
33+
* Nowdoc/Heredoc opener.
34+
* @param bool $stripQuotes Optional. Whether to strip text delimiter
35+
* quotes off the resulting text string.
36+
* Defaults to true.
37+
*
38+
* @return string
39+
*
40+
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a
41+
* valid text string token or if the
42+
* token is not the first text string token.
43+
*/
44+
public static function getCompleteTextString(File $phpcsFile, $stackPtr, $stripQuotes = true)
45+
{
46+
$tokens = $phpcsFile->getTokens();
47+
48+
// Must be the start of a text string token.
49+
if (isset($tokens[$stackPtr], Collections::$textStingStartTokens[$tokens[$stackPtr]['code']]) === false) {
50+
throw new RuntimeException(
51+
'$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING'
52+
. ' or T_DOUBLE_QUOTED_STRING'
53+
);
54+
}
55+
56+
if ($tokens[$stackPtr]['code'] === \T_CONSTANT_ENCAPSED_STRING
57+
|| $tokens[$stackPtr]['code'] === \T_DOUBLE_QUOTED_STRING
58+
) {
59+
$prev = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true);
60+
if ($tokens[$stackPtr]['code'] === $tokens[$prev]['code']) {
61+
throw new RuntimeException('$stackPtr must be the start of the text string');
62+
}
63+
}
64+
65+
switch ($tokens[$stackPtr]['code']) {
66+
case \T_START_HEREDOC:
67+
$stripQuotes = false;
68+
$targetType = \T_HEREDOC;
69+
$current = ($stackPtr + 1);
70+
break;
71+
72+
case \T_START_NOWDOC:
73+
$stripQuotes = false;
74+
$targetType = \T_NOWDOC;
75+
$current = ($stackPtr + 1);
76+
break;
77+
78+
default:
79+
$targetType = $tokens[$stackPtr]['code'];
80+
$current = $stackPtr;
81+
break;
82+
}
83+
84+
$string = '';
85+
do {
86+
$string .= $tokens[$current]['content'];
87+
++$current;
88+
} while (isset($tokens[$current]) && $tokens[$current]['code'] === $targetType);
89+
90+
if ($stripQuotes === true) {
91+
return self::stripQuotes($string);
92+
}
93+
94+
return $string;
95+
}
96+
97+
/**
98+
* Strip text delimiter quotes from an arbitrary string.
99+
*
100+
* Intended for use with the "contents" of a T_CONSTANT_ENCAPSED_STRING / T_DOUBLE_QUOTED_STRING.
101+
*
102+
* Prevents stripping mis-matched quotes.
103+
* Prevents stripping quotes from the textual content of the string.
104+
*
105+
* @since 1.0.0
106+
*
107+
* @param string $string The raw string.
108+
*
109+
* @return string String without quotes around it.
110+
*/
111+
public static function stripQuotes($string)
112+
{
113+
return \preg_replace('`^([\'"])(.*)\1$`Ds', '$2', $string);
114+
}
115+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/* testNotATextString */
4+
return $something;
5+
6+
/* testNotFirstTextStringToken */
7+
echo 'first line
8+
second line
9+
third line
10+
fourth line';
11+
12+
/* testSingleLineConstantEncapsedString */
13+
echo 'single line text string';
14+
15+
/* testMultiLineConstantEncapsedString */
16+
echo "first line
17+
second line
18+
third line
19+
fourth line";
20+
21+
/* testSingleLineDoubleQuotedString */
22+
echo "single $line text string";
23+
24+
/* testMultiLineDoubleQuotedString */
25+
echo "first line
26+
second $line
27+
third line
28+
fourth line";
29+
30+
/* testHeredocString */
31+
echo <<<EOD
32+
first line
33+
second $line
34+
third line
35+
fourth line
36+
EOD;
37+
38+
/* testNowdocString */
39+
echo <<<'EOD'
40+
first line
41+
second line
42+
third line
43+
fourth line
44+
EOD;
45+
46+
/* testTextStringAtEndOfFile */
47+
// This has to be the last test in the file without a new line after it.
48+
echo 'first line
49+
last line'
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Tests\Utils\TextStrings;
12+
13+
use PHPCSUtils\TestUtils\UtilityMethodTestCase;
14+
use PHPCSUtils\Utils\TextStrings;
15+
16+
/**
17+
* Tests for the \PHPCSUtils\Utils\TextStrings::getCompleteTextString() method.
18+
*
19+
* @covers \PHPCSUtils\Utils\TextStrings::getCompleteTextString
20+
*
21+
* @group textstrings
22+
*
23+
* @since 1.0.0
24+
*/
25+
class GetCompleteTextStringTest extends UtilityMethodTestCase
26+
{
27+
28+
/**
29+
* Token types to target for these tests.
30+
*
31+
* @var array
32+
*/
33+
private $targets = [
34+
\T_START_HEREDOC,
35+
\T_START_NOWDOC,
36+
\T_CONSTANT_ENCAPSED_STRING,
37+
\T_DOUBLE_QUOTED_STRING,
38+
];
39+
40+
/**
41+
* Test passing a non-existent token pointer.
42+
*
43+
* @return void
44+
*/
45+
public function testNonExistentToken()
46+
{
47+
$this->expectPhpcsException(
48+
'$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING'
49+
. ' or T_DOUBLE_QUOTED_STRING'
50+
);
51+
52+
TextStrings::getCompleteTextString(self::$phpcsFile, 100000);
53+
}
54+
55+
/**
56+
* Test receiving an expected exception when a non text string is passed.
57+
*
58+
* @return void
59+
*/
60+
public function testNotATextStringException()
61+
{
62+
$this->expectPhpcsException(
63+
'$stackPtr must be of type T_START_HEREDOC, T_START_NOWDOC, T_CONSTANT_ENCAPSED_STRING'
64+
. ' or T_DOUBLE_QUOTED_STRING'
65+
);
66+
67+
$next = $this->getTargetToken('/* testNotATextString */', \T_RETURN);
68+
TextStrings::getCompleteTextString(self::$phpcsFile, $next);
69+
}
70+
71+
/**
72+
* Test receiving an expected exception when a text string token is not the first token
73+
* of a multi-line text string.
74+
*
75+
* @return void
76+
*/
77+
public function testNotFirstTextStringException()
78+
{
79+
$this->expectPhpcsException('$stackPtr must be the start of the text string');
80+
81+
$next = $this->getTargetToken(
82+
'/* testNotFirstTextStringToken */',
83+
\T_CONSTANT_ENCAPSED_STRING,
84+
'second line
85+
'
86+
);
87+
TextStrings::getCompleteTextString(self::$phpcsFile, $next);
88+
}
89+
90+
/**
91+
* Test correctly retrieving the contents of a (potentially) multi-line text string.
92+
*
93+
* @dataProvider dataGetCompleteTextString
94+
*
95+
* @param string $testMarker The comment which prefaces the target token in the test file.
96+
* @param string $expected The expected function return value.
97+
* @param string $expectedWithQuotes The expected function return value when $stripQuotes is set to "false".
98+
*
99+
* @return void
100+
*/
101+
public function testGetCompleteTextString($testMarker, $expected, $expectedWithQuotes)
102+
{
103+
$stackPtr = $this->getTargetToken($testMarker, $this->targets);
104+
105+
$result = TextStrings::getCompleteTextString(self::$phpcsFile, $stackPtr);
106+
$this->assertSame($expected, $result, 'Test failed getting the correct string with quotes stripped');
107+
108+
$result = TextStrings::getCompleteTextString(self::$phpcsFile, $stackPtr, false);
109+
$this->assertSame($expectedWithQuotes, $result, 'Test failed getting the correct string (unchanged)');
110+
}
111+
112+
/**
113+
* Data provider.
114+
*
115+
* @see testGetCompleteTextString() For the array format.
116+
*
117+
* @return array
118+
*/
119+
public function dataGetCompleteTextString()
120+
{
121+
return [
122+
'single-line-constant-encapsed-string' => [
123+
'/* testSingleLineConstantEncapsedString */',
124+
'single line text string',
125+
"'single line text string'",
126+
],
127+
'multi-line-constant-encapsed-string' => [
128+
'/* testMultiLineConstantEncapsedString */',
129+
'first line
130+
second line
131+
third line
132+
fourth line',
133+
'"first line
134+
second line
135+
third line
136+
fourth line"',
137+
],
138+
'single-line-double-quoted-string' => [
139+
'/* testSingleLineDoubleQuotedString */',
140+
'single $line text string',
141+
'"single $line text string"',
142+
],
143+
'multi-line-double-quoted-string' => [
144+
'/* testMultiLineDoubleQuotedString */',
145+
'first line
146+
second $line
147+
third line
148+
fourth line',
149+
'"first line
150+
second $line
151+
third line
152+
fourth line"',
153+
],
154+
'heredoc' => [
155+
'/* testHeredocString */',
156+
'first line
157+
second $line
158+
third line
159+
fourth line
160+
',
161+
'first line
162+
second $line
163+
third line
164+
fourth line
165+
',
166+
],
167+
'nowdoc' => [
168+
'/* testNowdocString */',
169+
'first line
170+
second line
171+
third line
172+
fourth line
173+
',
174+
'first line
175+
second line
176+
third line
177+
fourth line
178+
',
179+
],
180+
'text-string-at-end-of-file' => [
181+
'/* testTextStringAtEndOfFile */',
182+
'first line
183+
last line',
184+
"'first line
185+
last line'",
186+
],
187+
];
188+
}
189+
}

0 commit comments

Comments
 (0)