Skip to content

Commit 3aca91e

Browse files
committed
fix: refactor version handling, change default to 8.4
This consolidates `options.lexer.version` and `options.parser.version` to a central `options.version`. `options.parser.version` still works, but is deprecated. This also updates the default version to 8.4.
1 parent 7d134e8 commit 3aca91e

File tree

15 files changed

+103
-108
lines changed

15 files changed

+103
-108
lines changed

src/index.js

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const parser = require("./parser");
1010
const tokens = require("./tokens");
1111
const AST = require("./ast");
1212

13+
const DEFAULT_PHP_VERSION = 804; // 8.4
14+
1315
/**
1416
* @private
1517
*/
@@ -43,10 +45,10 @@ function combine(src, to) {
4345
* @example
4446
* var parser = require('php-parser');
4547
* var instance = new parser({
48+
* version: 704 // or '7.4'
4649
* parser: {
4750
* extractDoc: true,
4851
* suppressErrors: true,
49-
* version: 704 // or '7.4'
5052
* },
5153
* ast: {
5254
* withPositions: true
@@ -81,30 +83,40 @@ const Engine = function (options) {
8183
if (!options.lexer) {
8284
options.lexer = {};
8385
}
84-
if (options.parser.version) {
85-
if (typeof options.parser.version === "string") {
86-
let version = options.parser.version.split(".");
87-
version = parseInt(version[0]) * 100 + parseInt(version[1]);
88-
if (isNaN(version)) {
89-
throw new Error("Bad version number : " + options.parser.version);
90-
} else {
91-
options.parser.version = version;
92-
}
93-
} else if (typeof options.parser.version !== "number") {
94-
throw new Error("Expecting a number for version");
95-
}
96-
if (options.parser.version < 500 || options.parser.version > 900) {
97-
throw new Error("Can only handle versions between 5.x to 8.x");
98-
}
99-
}
10086
}
10187
combine(options, this);
102-
103-
// same version flags based on parser options
104-
this.lexer.version = this.parser.version;
10588
}
89+
90+
// options.parser.version is deprecated, use options.version instead
91+
const versionString = options?.version ?? options?.parser?.version;
92+
this.version = normalizeVersion(versionString ?? DEFAULT_PHP_VERSION);
10693
};
10794

95+
/**
96+
* Validate and normalize a version (string or number) to a version number
97+
* @private
98+
* @param {String|Number} versionString - The version string or number to
99+
* validate and normalize, e.g., "7.4", or 704
100+
* @return {Number} - The normalized version number, e.g. 704
101+
* @throws {Error} - If the version is not a valid number or out of range
102+
*/
103+
function normalizeVersion(versionString) {
104+
let version = versionString;
105+
if (typeof version === "string") {
106+
const versionParts = version.split(".");
107+
version = parseInt(versionParts[0]) * 100 + parseInt(versionParts[1]);
108+
if (isNaN(version)) {
109+
throw new Error("Bad version number : " + versionString);
110+
}
111+
} else if (typeof version !== "number") {
112+
throw new Error("Expecting a string or number for version");
113+
}
114+
if (version < 500 || version > 900) {
115+
throw new Error("Can only handle versions between 5.x to 8.x");
116+
}
117+
return version;
118+
}
119+
108120
/**
109121
* Check if the inpyt is a buffer or a string
110122
* @private

src/lexer.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ const Lexer = function (engine) {
3030
this.mode_eval = false;
3131
this.asp_tags = false;
3232
this.short_tags = false;
33-
this.version = 803;
3433
this.yyprevcol = 0;
3534
this.keywords = {
3635
__class__: this.tok.T_CLASS_C,
@@ -150,7 +149,7 @@ Lexer.prototype.setInput = function (input) {
150149
last_column: 0,
151150
};
152151
this.tokens = [];
153-
if (this.version > 703) {
152+
if (this.engine.version > 703) {
154153
this.keywords.fn = this.tok.T_FN;
155154
} else {
156155
delete this.keywords.fn;

src/lexer/scripting.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = {
1616
case "\r\n":
1717
return this.T_WHITESPACE();
1818
case "#":
19-
if (this.version >= 800 && this._input[this.offset] === "[") {
19+
if (this.engine.version >= 800 && this._input[this.offset] === "[") {
2020
this.input();
2121
this.attributeListDepth[++this.attributeIndex] = 0;
2222
this.begin("ST_ATTRIBUTE");

src/lexer/strings.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ module.exports = {
159159
let indentation = 0;
160160
let leading_ch = this._input[offset - 1];
161161

162-
if (this.version >= 703) {
162+
if (this.engine.version >= 703) {
163163
while (leading_ch === "\t" || leading_ch === " ") {
164164
if (leading_ch === " ") {
165165
indentation_uses_spaces = true;
@@ -188,7 +188,7 @@ module.exports = {
188188
) {
189189
const ch = this._input[offset - 1 + this.heredoc_label.length];
190190
if (
191-
(this.version >= 703
191+
(this.engine.version >= 703
192192
? valid_after_heredoc_73
193193
: valid_after_heredoc
194194
).includes(ch)

src/lexer/tokens.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = {
1111
let id = this.keywords[token];
1212
if (typeof id !== "number") {
1313
if (token === "yield") {
14-
if (this.version >= 700 && this.tryMatch(" from")) {
14+
if (this.engine.version >= 700 && this.tryMatch(" from")) {
1515
this.consume(5);
1616
id = this.tok.T_YIELD_FROM;
1717
} else {
@@ -34,7 +34,7 @@ module.exports = {
3434

3535
// https://github.com/php/php-src/blob/master/Zend/zend_language_scanner.l#L1546
3636
if (id === this.tok.T_ENUM) {
37-
if (this.version < 801) {
37+
if (this.engine.version < 801) {
3838
return this.tok.T_STRING;
3939
}
4040
const initial = this.offset;
@@ -224,8 +224,11 @@ module.exports = {
224224
return "!";
225225
},
226226
"?"() {
227-
if (this.version >= 700 && this._input[this.offset] === "?") {
228-
if (this.version >= 704 && this._input[this.offset + 1] === "=") {
227+
if (this.engine.version >= 700 && this._input[this.offset] === "?") {
228+
if (
229+
this.engine.version >= 704 &&
230+
this._input[this.offset + 1] === "="
231+
) {
229232
this.consume(2);
230233
return this.tok.T_COALESCE_EQUAL;
231234
} else {
@@ -234,7 +237,7 @@ module.exports = {
234237
}
235238
}
236239
if (
237-
this.version >= 800 &&
240+
this.engine.version >= 800 &&
238241
this._input[this.offset] === "-" &&
239242
this._input[this.offset + 1] === ">"
240243
) {
@@ -260,7 +263,7 @@ module.exports = {
260263
return this.tok.T_SL;
261264
} else if (nchar === "=") {
262265
this.input();
263-
if (this.version >= 700 && this._input[this.offset] === ">") {
266+
if (this.engine.version >= 700 && this._input[this.offset] === ">") {
264267
this.input();
265268
return this.tok.T_SPACESHIP;
266269
} else {

src/parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ function isNumber(n) {
3030
*/
3131
const Parser = function (lexer, ast) {
3232
this.lexer = lexer;
33+
this.engine = lexer.engine;
3334
this.ast = ast;
3435
this.tok = lexer.tok;
3536
this.EOF = lexer.EOF;
3637
this.token = null;
3738
this.prev = null;
3839
this.debug = false;
39-
this.version = 803;
4040
this.extractDoc = false;
4141
this.extractTokens = false;
4242
this.suppressErrors = false;

src/parser/array.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ module.exports = {
8282
this.next();
8383
byRef = true;
8484
value = this.read_variable(true, false);
85-
} else if (this.token === this.tok.T_ELLIPSIS && this.version >= 704) {
85+
} else if (
86+
this.token === this.tok.T_ELLIPSIS &&
87+
this.engine.version >= 704
88+
) {
8689
this.next();
8790
if (this.token === "&") {
8891
this.error();

src/parser/class.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ module.exports = {
136136
} else if (
137137
allow_variables &&
138138
(this.token === this.tok.T_VARIABLE ||
139-
(this.version >= 801 && this.token === this.tok.T_READ_ONLY) ||
139+
(this.engine.version >= 801 && this.token === this.tok.T_READ_ONLY) ||
140140
// support https://wiki.php.net/rfc/typed_properties_v2
141-
(this.version >= 704 &&
141+
(this.engine.version >= 704 &&
142142
(this.token === "?" ||
143143
this.token === this.tok.T_ARRAY ||
144144
this.token === this.tok.T_CALLABLE ||
@@ -228,7 +228,7 @@ module.exports = {
228228
}
229229

230230
const [nullable, type] =
231-
this.version >= 803 ? this.read_optional_type() : [false, null];
231+
this.engine.version >= 803 ? this.read_optional_type() : [false, null];
232232

233233
const result = this.node("classconstant");
234234
const items = this.read_list(
@@ -246,7 +246,7 @@ module.exports = {
246246
let value = null;
247247
if (
248248
this.token === this.tok.T_STRING ||
249-
(this.version >= 700 && this.is("IDENTIFIER"))
249+
(this.engine.version >= 700 && this.is("IDENTIFIER"))
250250
) {
251251
constName = this.node("identifier");
252252
const name = this.text();
@@ -565,7 +565,7 @@ module.exports = {
565565
this.next();
566566
if (
567567
this.token === this.tok.T_STRING ||
568-
(this.version >= 700 && this.is("IDENTIFIER"))
568+
(this.engine.version >= 700 && this.is("IDENTIFIER"))
569569
) {
570570
trait = method;
571571
method = this.node("identifier");
@@ -599,7 +599,7 @@ module.exports = {
599599

600600
if (
601601
this.token === this.tok.T_STRING ||
602-
(this.version >= 700 && this.is("IDENTIFIER"))
602+
(this.engine.version >= 700 && this.is("IDENTIFIER"))
603603
) {
604604
alias = this.node("identifier");
605605
const name = this.text();

src/parser/expr.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,10 @@ module.exports = {
352352

353353
case this.tok.T_NEW:
354354
expr = this.read_new_expr();
355-
if (this.token === this.tok.T_OBJECT_OPERATOR && this.version < 804) {
355+
if (
356+
this.token === this.tok.T_OBJECT_OPERATOR &&
357+
this.engine.version < 804
358+
) {
356359
this.raiseError(
357360
"New without parenthesis is not allowed before PHP 8.4",
358361
);
@@ -394,7 +397,7 @@ module.exports = {
394397
return this.read_expr_cast("unset");
395398

396399
case this.tok.T_THROW: {
397-
if (this.version < 800) {
400+
if (this.engine.version < 800) {
398401
this.raiseError("PHP 8+ is required to use throw as an expression");
399402
}
400403
const result = this.node("throw");
@@ -444,7 +447,7 @@ module.exports = {
444447
this.next();
445448
if (
446449
this.token === this.tok.T_FUNCTION ||
447-
(this.version >= 704 && this.token === this.tok.T_FN)
450+
(this.engine.version >= 704 && this.token === this.tok.T_FN)
448451
) {
449452
// handles static function
450453
return this.read_inline_function([0, 1, 0], attrs);
@@ -595,7 +598,7 @@ module.exports = {
595598
this.next();
596599
let right;
597600
if (this.token === this.tok.T_NEW) {
598-
if (this.version >= 700) {
601+
if (this.engine.version >= 700) {
599602
this.error();
600603
}
601604
right = this.read_new_expr();
@@ -628,7 +631,7 @@ module.exports = {
628631
return result;
629632
}
630633
// introduced in PHP 7.4
631-
if (!this.version >= 704) {
634+
if (!this.engine.version >= 704) {
632635
this.raiseError("Arrow Functions are not allowed");
633636
}
634637
// as an arrowfunc
@@ -667,7 +670,7 @@ module.exports = {
667670
read_match_expression() {
668671
const node = this.node("match");
669672
this.expect(this.tok.T_MATCH) && this.next();
670-
if (this.version < 800) {
673+
if (this.engine.version < 800) {
671674
this.raiseError("Match statements are not allowed before PHP 8");
672675
}
673676
let cond = null;

src/parser/function.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ module.exports = {
8484
if (type !== 1) {
8585
const nameNode = this.node("identifier");
8686
if (type === 2) {
87-
if (this.version >= 700) {
87+
if (this.engine.version >= 700) {
8888
if (this.token === this.tok.T_STRING || this.is("IDENTIFIER")) {
8989
name = this.text();
9090
this.next();
91-
} else if (this.version < 704) {
91+
} else if (this.engine.version < 704) {
9292
this.error("IDENTIFIER");
9393
}
9494
} else if (this.token === this.tok.T_STRING) {
@@ -98,11 +98,11 @@ module.exports = {
9898
this.error("IDENTIFIER");
9999
}
100100
} else {
101-
if (this.version >= 700) {
101+
if (this.engine.version >= 700) {
102102
if (this.token === this.tok.T_STRING) {
103103
name = this.text();
104104
this.next();
105-
} else if (this.version >= 704) {
105+
} else if (this.engine.version >= 704) {
106106
if (!this.expect("(")) {
107107
this.next();
108108
}
@@ -178,7 +178,7 @@ module.exports = {
178178
result.push(item());
179179
if (this.token == ",") {
180180
this.next();
181-
if (this.version >= 800 && this.token === ")") {
181+
if (this.engine.version >= 800 && this.token === ")") {
182182
return result;
183183
}
184184
} else if (this.token == ")") {
@@ -258,7 +258,7 @@ module.exports = {
258258
let attrs = [];
259259
if (this.token === this.tok.T_ATTRIBUTE) attrs = this.read_attr_list();
260260

261-
if (this.version >= 801 && this.token === this.tok.T_READ_ONLY) {
261+
if (this.engine.version >= 801 && this.token === this.tok.T_READ_ONLY) {
262262
if (is_class_constructor) {
263263
this.next();
264264
readonly = true;
@@ -273,7 +273,7 @@ module.exports = {
273273

274274
if (
275275
!readonly &&
276-
this.version >= 801 &&
276+
this.engine.version >= 801 &&
277277
this.token === this.tok.T_READ_ONLY
278278
) {
279279
if (is_class_constructor) {
@@ -336,7 +336,10 @@ module.exports = {
336336
// is the current token a:
337337
// - | for union type
338338
// - & for intersection type (> php 8.1)
339-
while (this.token === "|" || (this.version >= 801 && this.token === "&")) {
339+
while (
340+
this.token === "|" ||
341+
(this.engine.version >= 801 && this.token === "&")
342+
) {
340343
const nextToken = this.peek();
341344

342345
if (
@@ -400,7 +403,7 @@ module.exports = {
400403
let result = [];
401404
this.expect("(") && this.next();
402405
if (
403-
this.version >= 801 &&
406+
this.engine.version >= 801 &&
404407
this.token === this.tok.T_ELLIPSIS &&
405408
this.peek() === ")"
406409
) {
@@ -453,7 +456,7 @@ module.exports = {
453456
) {
454457
const nextToken = this.peek();
455458
if (nextToken === ":") {
456-
if (this.version < 800) {
459+
if (this.engine.version < 800) {
457460
this.raiseError("PHP 8+ is required to use named arguments");
458461
}
459462
return this.node("namedargument")(

0 commit comments

Comments
 (0)