Skip to content

Commit 4a85ee9

Browse files
committed
throw exceptions instead of triggering warnings
1 parent bac3bfe commit 4a85ee9

File tree

3 files changed

+108
-49
lines changed

3 files changed

+108
-49
lines changed

src/Exception.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace splitbrain\JSStrip;
4+
5+
class Exception extends \Exception
6+
{
7+
}

src/JSStrip.php

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,49 +30,59 @@ class JSStrip
3030
*/
3131
const OPS = "+-/";
3232

33+
protected $source;
34+
protected $idx = 0;
35+
protected $line = 0;
36+
3337
/**
3438
* Compress the given code
35-
*
39+
*
3640
* @param string $source The JavaScript code to compress
3741
* @return string
42+
* @throws Exception if parsing fails
3843
*/
3944
function compress($source)
4045
{
4146
$source = ltrim($source); // strip all initial whitespace
4247
$source .= "\n";
43-
$i = 0; // char index for input string
48+
$idx = 0; // char index for input string
49+
50+
// track these as member variables
51+
$this->source = $source;
52+
$this->line = 1;
53+
$this->idx = &$idx;
54+
4455
$j = 0; // char forward index for input string
45-
$line = 0; // line number of file (close to it anyways)
4656
$slen = strlen($source); // size of input string
4757
$lch = ''; // last char added
4858
$result = ''; // we store the final result here
4959

5060

51-
while ($i < $slen) {
61+
while ($idx < $slen) {
5262
// skip all "boring" characters. This is either
5363
// reserved word (e.g. "for", "else", "if") or a
5464
// variable/object/method (e.g. "foo.color")
55-
while ($i < $slen && (strpos(self::CHARS, $source[$i]) === false)) {
56-
$result .= $source[$i];
57-
$i = $i + 1;
65+
while ($idx < $slen && (strpos(self::CHARS, $source[$idx]) === false)) {
66+
$result .= $source[$idx];
67+
$idx = $idx + 1;
5868
}
5969

60-
$ch = $source[$i];
70+
$ch = $source[$idx];
6171
// multiline comments (keeping IE conditionals)
62-
if ($ch == '/' && $source[$i + 1] == '*' && $source[$i + 2] != '@') {
63-
$endC = strpos($source, '*/', $i + 2);
64-
if ($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR);
72+
if ($ch == '/' && $source[$idx + 1] == '*' && $source[$idx + 2] != '@') {
73+
$endC = strpos($source, '*/', $idx + 2);
74+
if ($endC === false) $this->fatal('Found invalid /*..*/ comment');
6575

6676
// check if this is a NOCOMPRESS comment
67-
if (substr($source, $i, $endC + 2 - $i) == '/* BEGIN NOCOMPRESS */') {
77+
if (substr($source, $idx, $endC + 2 - $idx) == '/* BEGIN NOCOMPRESS */') {
6878
// take nested NOCOMPRESS comments into account
6979
$depth = 0;
7080
$nextNC = $endC;
7181
do {
7282
$beginNC = strpos($source, '/* BEGIN NOCOMPRESS */', $nextNC + 2);
7383
$endNC = strpos($source, '/* END NOCOMPRESS */', $nextNC + 2);
7484

75-
if ($endNC === false) trigger_error('Found invalid NOCOMPRESS comment', E_USER_ERROR);
85+
if ($endNC === false) $this->fatal('Found invalid NOCOMPRESS comment');
7686
if ($beginNC !== false && $beginNC < $endNC) {
7787
$depth++;
7888
$nextNC = $beginNC;
@@ -83,34 +93,34 @@ function compress($source)
8393
} while ($depth >= 0);
8494

8595
// verbatim copy contents, trimming but putting it on its own line
86-
$result .= "\n" . trim(substr($source, $i + 22, $endNC - ($i + 22))) . "\n"; // BEGIN comment = 22 chars
87-
$i = $endNC + 20; // END comment = 20 chars
96+
$result .= "\n" . trim(substr($source, $idx + 22, $endNC - ($idx + 22))) . "\n"; // BEGIN comment = 22 chars
97+
$idx = $endNC + 20; // END comment = 20 chars
8898
} else {
89-
$i = $endC + 2;
99+
$idx = $endC + 2;
90100
}
91101
continue;
92102
}
93103

94104
// singleline
95-
if ($ch == '/' && $source[$i + 1] == '/') {
96-
$endC = strpos($source, "\n", $i + 2);
97-
if ($endC === false) trigger_error('Invalid comment', E_USER_ERROR);
98-
$i = $endC;
105+
if ($ch == '/' && $source[$idx + 1] == '/') {
106+
$endC = strpos($source, "\n", $idx + 2);
107+
if ($endC === false) $this->fatal('Invalid comment'); // not sure this can happen
108+
$idx = $endC;
99109
continue;
100110
}
101111

102112
// tricky. might be an RE
103113
if ($ch == '/') {
104114
// rewind, skip white space
105115
$j = 1;
106-
while (in_array($source[$i - $j], self::WHITESPACE_CHARS)) {
116+
while (in_array($source[$idx - $j], self::WHITESPACE_CHARS)) {
107117
$j = $j + 1;
108118
}
109119
if (current(array_filter(
110120
self::REGEX_STARTERS,
111-
function ($e) use ($source, $i, $j) {
121+
function ($e) use ($source, $idx, $j) {
112122
$len = strlen($e);
113-
$idx = $i - $j + 1 - $len;
123+
$idx = $idx - $j + 1 - $len;
114124
return substr($source, $idx, $len) === $e;
115125
}
116126
))) {
@@ -119,94 +129,95 @@ function ($e) use ($source, $i, $j) {
119129
$j = 1;
120130
// we set this flag when inside a character class definition, enclosed by brackets [] where '/' does not terminate the re
121131
$ccd = false;
122-
while ($ccd || $source[$i + $j] != '/') {
123-
if ($source[$i + $j] == '\\') $j = $j + 2;
132+
while ($ccd || $source[$idx + $j] != '/') {
133+
if ($source[$idx + $j] == '\\') $j = $j + 2;
124134
else {
125135
$j++;
126136
// check if we entered/exited a character class definition and set flag accordingly
127-
if ($source[$i + $j - 1] == '[') $ccd = true;
128-
else if ($source[$i + $j - 1] == ']') $ccd = false;
137+
if ($source[$idx + $j - 1] == '[') $ccd = true;
138+
else if ($source[$idx + $j - 1] == ']') $ccd = false;
129139
}
130140
}
131-
$result .= substr($source, $i, $j + 1);
132-
$i = $i + $j + 1;
141+
$result .= substr($source, $idx, $j + 1);
142+
$idx = $idx + $j + 1;
133143
continue;
134144
}
135145
}
136146

137147
// double quote strings
138148
if ($ch == '"') {
139149
$j = 1;
140-
while (($i + $j < $slen) && $source[$i + $j] != '"') {
141-
if ($source[$i + $j] == '\\' && ($source[$i + $j + 1] == '"' || $source[$i + $j + 1] == '\\')) {
150+
while (($idx + $j < $slen) && $source[$idx + $j] != '"') {
151+
if ($source[$idx + $j] == '\\' && ($source[$idx + $j + 1] == '"' || $source[$idx + $j + 1] == '\\')) {
142152
$j += 2;
143153
} else {
144154
$j += 1;
145155
}
146156
}
147-
$string = substr($source, $i, $j + 1);
157+
$string = substr($source, $idx, $j + 1);
148158
// remove multiline markers:
149159
$string = str_replace("\\\n", '', $string);
150160
$result .= $string;
151-
$i = $i + $j + 1;
161+
$idx = $idx + $j + 1;
152162
continue;
153163
}
154164

155165
// single quote strings
156166
if ($ch == "'") {
157167
$j = 1;
158-
while (($i + $j < $slen) && $source[$i + $j] != "'") {
159-
if ($source[$i + $j] == '\\' && ($source[$i + $j + 1] == "'" || $source[$i + $j + 1] == '\\')) {
168+
while (($idx + $j < $slen) && $source[$idx + $j] != "'") {
169+
if ($source[$idx + $j] == '\\' && ($source[$idx + $j + 1] == "'" || $source[$idx + $j + 1] == '\\')) {
160170
$j += 2;
161171
} else {
162172
$j += 1;
163173
}
164174
}
165-
$string = substr($source, $i, $j + 1);
175+
$string = substr($source, $idx, $j + 1);
166176
// remove multiline markers:
167177
$string = str_replace("\\\n", '', $string);
168178
$result .= $string;
169-
$i = $i + $j + 1;
179+
$idx = $idx + $j + 1;
170180
continue;
171181
}
172182

173183
// backtick strings
174184
if ($ch == "`") {
175185
$j = 1;
176-
while (($i + $j < $slen) && $source[$i + $j] != "`") {
177-
if ($source[$i + $j] == '\\' && ($source[$i + $j + 1] == "`" || $source[$i + $j + 1] == '\\')) {
186+
while (($idx + $j < $slen) && $source[$idx + $j] != "`") {
187+
if ($source[$idx + $j] == '\\' && ($source[$idx + $j + 1] == "`" || $source[$idx + $j + 1] == '\\')) {
178188
$j += 2;
179189
} else {
180190
$j += 1;
181191
}
182192
}
183-
$string = substr($source, $i, $j + 1);
193+
$string = substr($source, $idx, $j + 1);
184194
// remove multiline markers:
185195
$string = str_replace("\\\n", '', $string);
186196
$result .= $string;
187-
$i = $i + $j + 1;
197+
$idx = $idx + $j + 1;
188198
continue;
189199
}
190200

191201
// whitespaces
192202
if ($ch == ' ' || $ch == "\r" || $ch == "\n" || $ch == "\t") {
193203
$lch = substr($result, -1);
204+
if ($ch == "\n") $this->line++;
194205

195206
// Only consider deleting whitespace if the signs before and after
196207
// are not equal and are not an operator which may not follow itself.
197-
if ($i + 1 < $slen && ((!$lch || $source[$i + 1] == ' ')
198-
|| $lch != $source[$i + 1]
199-
|| strpos(self::OPS, $source[$i + 1]) === false)) {
208+
if ($idx + 1 < $slen && ((!$lch || $source[$idx + 1] == ' ')
209+
|| $lch != $source[$idx + 1]
210+
|| strpos(self::OPS, $source[$idx + 1]) === false)) {
200211
// leading spaces
201-
if ($i + 1 < $slen && (strpos(self::CHARS, $source[$i + 1]) !== false)) {
202-
$i = $i + 1;
212+
if ($idx + 1 < $slen && (strpos(self::CHARS, $source[$idx + 1]) !== false)) {
213+
$idx = $idx + 1;
203214
continue;
204215
}
205216
// trailing spaces
206217
// if this ch is space AND the last char processed
207218
// is special, then skip the space
208219
if ($lch && (strpos(self::CHARS, $lch) !== false)) {
209-
$i = $i + 1;
220+
$idx = $idx + 1;
210221
continue;
211222
}
212223
}
@@ -218,9 +229,26 @@ function ($e) use ($source, $i, $j) {
218229

219230
// other chars
220231
$result .= $ch;
221-
$i = $i + 1;
232+
$idx = $idx + 1;
222233
}
223234

224235
return trim($result);
225236
}
237+
238+
/**
239+
* Helper to throw a fatal error
240+
*
241+
* Tries to give some context to locate the error
242+
*
243+
* @param string $msg
244+
* @throws Exception
245+
*/
246+
protected function fatal($msg)
247+
{
248+
$before = substr($this->source, max(0, $this->idx - 15), $this->idx);
249+
$after = substr($this->source, $this->idx, 15);
250+
251+
$msg = "$msg on line {$this->line}: '{$before}{$after}'";
252+
throw new Exception($msg);
253+
}
226254
}

tests/JSStripTest.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace splitbrain\JSStrip\tests;
44

55
use PHPUnit\Framework\TestCase;
6+
use splitbrain\JSStrip\Exception;
67
use splitbrain\JSStrip\JSStrip;
78

89
class JSStripTest extends TestCase
@@ -132,8 +133,31 @@ public function provideFileData()
132133
* @param string $output
133134
* @param string $file
134135
*/
135-
function testFileData($input, $output, $file)
136+
public function testFileData($input, $output, $file)
136137
{
137138
$this->assertEquals($output, (new JSStrip())->compress($input), $file);
138139
}
140+
141+
/**
142+
* Test cases that should throw an exception
143+
*
144+
* @return array[] [input]
145+
* @see testException
146+
*/
147+
public function provideException() {
148+
return [
149+
['foo /* BEGIN NOCOMPRESS */ we never end this block'],
150+
["/* we never end this comment"],
151+
];
152+
}
153+
154+
/**
155+
* @dataProvider provideException
156+
* @param string $input
157+
*/
158+
public function testException($input)
159+
{
160+
$this->expectException(Exception::class);
161+
(new JSStrip())->compress($input);
162+
}
139163
}

0 commit comments

Comments
 (0)