Skip to content

Commit c9fea2e

Browse files
committed
Extend Name::slice() to support negative length+offset
1 parent 818ef2e commit c9fea2e

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

lib/PhpParser/Node/Name.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,31 @@ public function setLast($name) {
165165
* If the slice is empty, a Name with an empty parts array is returned. While this is
166166
* meaningless in itself, it works correctly in conjunction with concat().
167167
*
168-
* @param int $offset Offset to start the slice at
168+
* Offset and length have the same meaning as in array_slice().
169+
*
170+
* @param int $offset Offset to start the slice at (may be negative)
171+
* @param int|null $length Length of the slice (may be negative)
169172
*
170173
* @return static Sliced name
171174
*/
172-
public function slice($offset) {
173-
// TODO negative offset and length
174-
if ($offset < 0 || $offset > count($this->parts)) {
175+
public function slice($offset, $length = null) {
176+
$numParts = count($this->parts);
177+
178+
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
179+
if ($realOffset < 0 || $realOffset > $numParts) {
175180
throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
176181
}
177182

178-
return new static(array_slice($this->parts, $offset), $this->attributes);
183+
if (null === $length) {
184+
$realLength = $numParts - $realOffset;
185+
} else {
186+
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
187+
if ($realLength < 0 || $realLength > $numParts) {
188+
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
189+
}
190+
}
191+
192+
return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
179193
}
180194

181195
/**

test/PhpParser/Node/NameTest.php

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,53 @@ public function testPrepend() {
9696
}
9797

9898
public function testSlice() {
99-
$name = new Name('foo\bar');
100-
$this->assertEquals(new Name('foo\bar'), $name->slice(0));
101-
$this->assertEquals(new Name('bar'), $name->slice(1));
102-
$this->assertEquals(new Name([]), $name->slice(2));
99+
$name = new Name('foo\bar\baz');
100+
$this->assertEquals(new Name('foo\bar\baz'), $name->slice(0));
101+
$this->assertEquals(new Name('bar\baz'), $name->slice(1));
102+
$this->assertEquals(new Name([]), $name->slice(3));
103+
$this->assertEquals(new Name('foo\bar\baz'), $name->slice(-3));
104+
$this->assertEquals(new Name('bar\baz'), $name->slice(-2));
105+
$this->assertEquals(new Name('foo\bar'), $name->slice(0, -1));
106+
$this->assertEquals(new Name([]), $name->slice(0, -3));
107+
$this->assertEquals(new Name('bar'), $name->slice(1, -1));
108+
$this->assertEquals(new Name([]), $name->slice(1, -2));
109+
$this->assertEquals(new Name('bar'), $name->slice(-2, 1));
110+
$this->assertEquals(new Name('bar'), $name->slice(-2, -1));
111+
$this->assertEquals(new Name([]), $name->slice(-2, -2));
103112
}
104113

105114
/**
106115
* @expectedException \OutOfBoundsException
107116
* @expectedExceptionMessage Offset 4 is out of bounds
108117
*/
109-
public function testSliceException() {
118+
public function testSliceOffsetTooLarge() {
110119
(new Name('foo\bar\baz'))->slice(4);
111120
}
112121

122+
/**
123+
* @expectedException \OutOfBoundsException
124+
* @expectedExceptionMessage Offset -4 is out of bounds
125+
*/
126+
public function testSliceOffsetTooSmall() {
127+
(new Name('foo\bar\baz'))->slice(-4);
128+
}
129+
130+
/**
131+
* @expectedException \OutOfBoundsException
132+
* @expectedExceptionMessage Length 4 is out of bounds
133+
*/
134+
public function testSliceLengthTooLarge() {
135+
(new Name('foo\bar\baz'))->slice(0, 4);
136+
}
137+
138+
/**
139+
* @expectedException \OutOfBoundsException
140+
* @expectedExceptionMessage Length -4 is out of bounds
141+
*/
142+
public function testSliceLengthTooSmall() {
143+
(new Name('foo\bar\baz'))->slice(0, -4);
144+
}
145+
113146
public function testConcat() {
114147
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
115148
$this->assertEquals(

0 commit comments

Comments
 (0)