Skip to content

fix: refactor version handling, change default to 8.4 #1163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const engine = require("php-parser");
// initialize a new parser instance
const parser = new engine({
// some options :
version: "8.4", // specify the PHP version to parse
parser: {
extractDoc: true,
php7: true,
Expand Down
52 changes: 32 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const parser = require("./parser");
const tokens = require("./tokens");
const AST = require("./ast");

const DEFAULT_PHP_VERSION = "8.4";

/**
* @private
*/
Expand Down Expand Up @@ -43,10 +45,10 @@ function combine(src, to) {
* @example
* var parser = require('php-parser');
* var instance = new parser({
* version: 704 // or '7.4'
* parser: {
* extractDoc: true,
* suppressErrors: true,
* version: 704 // or '7.4'
* },
* ast: {
* withPositions: true
Expand Down Expand Up @@ -81,30 +83,40 @@ const Engine = function (options) {
if (!options.lexer) {
options.lexer = {};
}
if (options.parser.version) {
if (typeof options.parser.version === "string") {
let version = options.parser.version.split(".");
version = parseInt(version[0]) * 100 + parseInt(version[1]);
if (isNaN(version)) {
throw new Error("Bad version number : " + options.parser.version);
} else {
options.parser.version = version;
}
} else if (typeof options.parser.version !== "number") {
throw new Error("Expecting a number for version");
}
if (options.parser.version < 500 || options.parser.version > 900) {
throw new Error("Can only handle versions between 5.x to 8.x");
}
}
}
combine(options, this);

// same version flags based on parser options
this.lexer.version = this.parser.version;
}

// options.parser.version is deprecated, use options.version instead
const versionString = options?.version ?? options?.parser?.version;
this.version = normalizeVersion(versionString ?? DEFAULT_PHP_VERSION);
};

/**
* Validate and normalize a version (string or number) to a version number
* @private
* @param {String|Number} versionString - The version string or number to
* validate and normalize, e.g., "7.4", or 704
* @return {Number} - The normalized version number, e.g. 704
* @throws {Error} - If the version is not a valid number or out of range
*/
function normalizeVersion(versionString) {
let version = versionString;
if (typeof version === "string") {
const versionParts = version.split(".");
version = parseInt(versionParts[0]) * 100 + parseInt(versionParts[1]);
if (isNaN(version)) {
throw new Error("Bad version number : " + versionString);
}
} else if (typeof version !== "number") {
throw new Error("Expecting a string or number for version");
}
if (version < 500 || version > 900) {
throw new Error("Can only handle versions between 5.x to 8.x");
}
return version;
}

/**
* Check if the inpyt is a buffer or a string
* @private
Expand Down
3 changes: 1 addition & 2 deletions src/lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const Lexer = function (engine) {
this.mode_eval = false;
this.asp_tags = false;
this.short_tags = false;
this.version = 803;
this.yyprevcol = 0;
this.keywords = {
__class__: this.tok.T_CLASS_C,
Expand Down Expand Up @@ -150,7 +149,7 @@ Lexer.prototype.setInput = function (input) {
last_column: 0,
};
this.tokens = [];
if (this.version > 703) {
if (this.engine.version > 703) {
this.keywords.fn = this.tok.T_FN;
} else {
delete this.keywords.fn;
Expand Down
2 changes: 1 addition & 1 deletion src/lexer/scripting.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {
case "\r\n":
return this.T_WHITESPACE();
case "#":
if (this.version >= 800 && this._input[this.offset] === "[") {
if (this.engine.version >= 800 && this._input[this.offset] === "[") {
this.input();
this.attributeListDepth[++this.attributeIndex] = 0;
this.begin("ST_ATTRIBUTE");
Expand Down
4 changes: 2 additions & 2 deletions src/lexer/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ module.exports = {
let indentation = 0;
let leading_ch = this._input[offset - 1];

if (this.version >= 703) {
if (this.engine.version >= 703) {
while (leading_ch === "\t" || leading_ch === " ") {
if (leading_ch === " ") {
indentation_uses_spaces = true;
Expand Down Expand Up @@ -188,7 +188,7 @@ module.exports = {
) {
const ch = this._input[offset - 1 + this.heredoc_label.length];
if (
(this.version >= 703
(this.engine.version >= 703
? valid_after_heredoc_73
: valid_after_heredoc
).includes(ch)
Expand Down
15 changes: 9 additions & 6 deletions src/lexer/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = {
let id = this.keywords[token];
if (typeof id !== "number") {
if (token === "yield") {
if (this.version >= 700 && this.tryMatch(" from")) {
if (this.engine.version >= 700 && this.tryMatch(" from")) {
this.consume(5);
id = this.tok.T_YIELD_FROM;
} else {
Expand All @@ -34,7 +34,7 @@ module.exports = {

// https://github.com/php/php-src/blob/master/Zend/zend_language_scanner.l#L1546
if (id === this.tok.T_ENUM) {
if (this.version < 801) {
if (this.engine.version < 801) {
return this.tok.T_STRING;
}
const initial = this.offset;
Expand Down Expand Up @@ -224,8 +224,11 @@ module.exports = {
return "!";
},
"?"() {
if (this.version >= 700 && this._input[this.offset] === "?") {
if (this.version >= 704 && this._input[this.offset + 1] === "=") {
if (this.engine.version >= 700 && this._input[this.offset] === "?") {
if (
this.engine.version >= 704 &&
this._input[this.offset + 1] === "="
) {
this.consume(2);
return this.tok.T_COALESCE_EQUAL;
} else {
Expand All @@ -234,7 +237,7 @@ module.exports = {
}
}
if (
this.version >= 800 &&
this.engine.version >= 800 &&
this._input[this.offset] === "-" &&
this._input[this.offset + 1] === ">"
) {
Expand All @@ -260,7 +263,7 @@ module.exports = {
return this.tok.T_SL;
} else if (nchar === "=") {
this.input();
if (this.version >= 700 && this._input[this.offset] === ">") {
if (this.engine.version >= 700 && this._input[this.offset] === ">") {
this.input();
return this.tok.T_SPACESHIP;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ function isNumber(n) {
*/
const Parser = function (lexer, ast) {
this.lexer = lexer;
this.engine = lexer.engine;
this.ast = ast;
this.tok = lexer.tok;
this.EOF = lexer.EOF;
this.token = null;
this.prev = null;
this.debug = false;
this.version = 803;
this.extractDoc = false;
this.extractTokens = false;
this.suppressErrors = false;
Expand Down
5 changes: 4 additions & 1 deletion src/parser/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ module.exports = {
this.next();
byRef = true;
value = this.read_variable(true, false);
} else if (this.token === this.tok.T_ELLIPSIS && this.version >= 704) {
} else if (
this.token === this.tok.T_ELLIPSIS &&
this.engine.version >= 704
) {
this.next();
if (this.token === "&") {
this.error();
Expand Down
12 changes: 6 additions & 6 deletions src/parser/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ module.exports = {
} else if (
allow_variables &&
(this.token === this.tok.T_VARIABLE ||
(this.version >= 801 && this.token === this.tok.T_READ_ONLY) ||
(this.engine.version >= 801 && this.token === this.tok.T_READ_ONLY) ||
// support https://wiki.php.net/rfc/typed_properties_v2
(this.version >= 704 &&
(this.engine.version >= 704 &&
(this.token === "?" ||
this.token === this.tok.T_ARRAY ||
this.token === this.tok.T_CALLABLE ||
Expand Down Expand Up @@ -228,7 +228,7 @@ module.exports = {
}

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

const result = this.node("classconstant");
const items = this.read_list(
Expand All @@ -246,7 +246,7 @@ module.exports = {
let value = null;
if (
this.token === this.tok.T_STRING ||
(this.version >= 700 && this.is("IDENTIFIER"))
(this.engine.version >= 700 && this.is("IDENTIFIER"))
) {
constName = this.node("identifier");
const name = this.text();
Expand Down Expand Up @@ -565,7 +565,7 @@ module.exports = {
this.next();
if (
this.token === this.tok.T_STRING ||
(this.version >= 700 && this.is("IDENTIFIER"))
(this.engine.version >= 700 && this.is("IDENTIFIER"))
) {
trait = method;
method = this.node("identifier");
Expand Down Expand Up @@ -599,7 +599,7 @@ module.exports = {

if (
this.token === this.tok.T_STRING ||
(this.version >= 700 && this.is("IDENTIFIER"))
(this.engine.version >= 700 && this.is("IDENTIFIER"))
) {
alias = this.node("identifier");
const name = this.text();
Expand Down
15 changes: 9 additions & 6 deletions src/parser/expr.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,10 @@ module.exports = {

case this.tok.T_NEW:
expr = this.read_new_expr();
if (this.token === this.tok.T_OBJECT_OPERATOR && this.version < 804) {
if (
this.token === this.tok.T_OBJECT_OPERATOR &&
this.engine.version < 804
) {
this.raiseError(
"New without parenthesis is not allowed before PHP 8.4",
);
Expand Down Expand Up @@ -394,7 +397,7 @@ module.exports = {
return this.read_expr_cast("unset");

case this.tok.T_THROW: {
if (this.version < 800) {
if (this.engine.version < 800) {
this.raiseError("PHP 8+ is required to use throw as an expression");
}
const result = this.node("throw");
Expand Down Expand Up @@ -444,7 +447,7 @@ module.exports = {
this.next();
if (
this.token === this.tok.T_FUNCTION ||
(this.version >= 704 && this.token === this.tok.T_FN)
(this.engine.version >= 704 && this.token === this.tok.T_FN)
) {
// handles static function
return this.read_inline_function([0, 1, 0], attrs);
Expand Down Expand Up @@ -595,7 +598,7 @@ module.exports = {
this.next();
let right;
if (this.token === this.tok.T_NEW) {
if (this.version >= 700) {
if (this.engine.version >= 700) {
this.error();
}
right = this.read_new_expr();
Expand Down Expand Up @@ -628,7 +631,7 @@ module.exports = {
return result;
}
// introduced in PHP 7.4
if (!this.version >= 704) {
if (!this.engine.version >= 704) {
this.raiseError("Arrow Functions are not allowed");
}
// as an arrowfunc
Expand Down Expand Up @@ -667,7 +670,7 @@ module.exports = {
read_match_expression() {
const node = this.node("match");
this.expect(this.tok.T_MATCH) && this.next();
if (this.version < 800) {
if (this.engine.version < 800) {
this.raiseError("Match statements are not allowed before PHP 8");
}
let cond = null;
Expand Down
23 changes: 13 additions & 10 deletions src/parser/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ module.exports = {
if (type !== 1) {
const nameNode = this.node("identifier");
if (type === 2) {
if (this.version >= 700) {
if (this.engine.version >= 700) {
if (this.token === this.tok.T_STRING || this.is("IDENTIFIER")) {
name = this.text();
this.next();
} else if (this.version < 704) {
} else if (this.engine.version < 704) {
this.error("IDENTIFIER");
}
} else if (this.token === this.tok.T_STRING) {
Expand All @@ -98,11 +98,11 @@ module.exports = {
this.error("IDENTIFIER");
}
} else {
if (this.version >= 700) {
if (this.engine.version >= 700) {
if (this.token === this.tok.T_STRING) {
name = this.text();
this.next();
} else if (this.version >= 704) {
} else if (this.engine.version >= 704) {
if (!this.expect("(")) {
this.next();
}
Expand Down Expand Up @@ -178,7 +178,7 @@ module.exports = {
result.push(item());
if (this.token == ",") {
this.next();
if (this.version >= 800 && this.token === ")") {
if (this.engine.version >= 800 && this.token === ")") {
return result;
}
} else if (this.token == ")") {
Expand Down Expand Up @@ -258,7 +258,7 @@ module.exports = {
let attrs = [];
if (this.token === this.tok.T_ATTRIBUTE) attrs = this.read_attr_list();

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

if (
!readonly &&
this.version >= 801 &&
this.engine.version >= 801 &&
this.token === this.tok.T_READ_ONLY
) {
if (is_class_constructor) {
Expand Down Expand Up @@ -336,7 +336,10 @@ module.exports = {
// is the current token a:
// - | for union type
// - & for intersection type (> php 8.1)
while (this.token === "|" || (this.version >= 801 && this.token === "&")) {
while (
this.token === "|" ||
(this.engine.version >= 801 && this.token === "&")
) {
const nextToken = this.peek();

if (
Expand Down Expand Up @@ -400,7 +403,7 @@ module.exports = {
let result = [];
this.expect("(") && this.next();
if (
this.version >= 801 &&
this.engine.version >= 801 &&
this.token === this.tok.T_ELLIPSIS &&
this.peek() === ")"
) {
Expand Down Expand Up @@ -453,7 +456,7 @@ module.exports = {
) {
const nextToken = this.peek();
if (nextToken === ":") {
if (this.version < 800) {
if (this.engine.version < 800) {
this.raiseError("PHP 8+ is required to use named arguments");
}
return this.node("namedargument")(
Expand Down
Loading