UNPKG

php-parser

Version:

Parse PHP code from JS and returns its AST

320 lines (311 loc) 8.4 kB
/** * Copyright (C) 2018 Glayzzle (BSD3 License) * @authors https://github.com/glayzzle/php-parser/graphs/contributors * @url http://glayzzle.com */ "use strict"; module.exports = { /* * checks if current token is a reference keyword */ is_reference: function () { if (this.token == "&") { this.next(); return true; } return false; }, /* * checks if current token is a variadic keyword */ is_variadic: function () { if (this.token === this.tok.T_ELLIPSIS) { this.next(); return true; } return false; }, /* * reading a function * ```ebnf * function ::= function_declaration code_block * ``` */ read_function: function (closure, flag) { const result = this.read_function_declaration( closure ? 1 : flag ? 2 : 0, flag && flag[1] === 1 ); if (flag && flag[2] == 1) { // abstract function : result.parseFlags(flag); if (this.expect(";")) { this.next(); } } else { if (this.expect("{")) { result.body = this.read_code_block(false); if (result.loc && result.body.loc) { result.loc.end = result.body.loc.end; } } if (!closure && flag) { result.parseFlags(flag); } } return result; }, /* * reads a function declaration (without his body) * ```ebnf * function_declaration ::= T_FUNCTION '&'? T_STRING '(' parameter_list ')' * ``` */ read_function_declaration: function (type, isStatic) { let nodeName = "function"; if (type === 1) { nodeName = "closure"; } else if (type === 2) { nodeName = "method"; } const result = this.node(nodeName); if (this.expect(this.tok.T_FUNCTION)) { this.next(); } const isRef = this.is_reference(); let name = false, use = [], returnType = null, nullable = false; if (type !== 1) { const nameNode = this.node("identifier"); if (type === 2) { if (this.version >= 700) { if (this.token === this.tok.T_STRING || this.is("IDENTIFIER")) { name = this.text(); this.next(); } else if (this.version < 704) { this.error("IDENTIFIER"); } } else if (this.token === this.tok.T_STRING) { name = this.text(); this.next(); } else { this.error("IDENTIFIER"); } } else { if (this.version >= 700) { if (this.token === this.tok.T_STRING) { name = this.text(); this.next(); } else if (this.version >= 704) { if (!this.expect("(")) { this.next(); } } else { this.error(this.tok.T_STRING); this.next(); } } else { if (this.expect(this.tok.T_STRING)) { name = this.text(); } this.next(); } } name = nameNode(name); } if (this.expect("(")) this.next(); const params = this.read_parameter_list(); if (this.expect(")")) this.next(); if (type === 1) { use = this.read_lexical_vars(); } if (this.token === ":") { if (this.next().token === "?") { nullable = true; this.next(); } returnType = this.read_type(); } if (type === 1) { // closure return result(params, isRef, use, returnType, nullable, isStatic); } return result(name, params, isRef, returnType, nullable); }, read_lexical_vars: function () { let result = []; if (this.token === this.tok.T_USE) { this.next(); this.expect("(") && this.next(); result = this.read_lexical_var_list(); this.expect(")") && this.next(); } return result; }, read_lexical_var_list: function () { return this.read_list(this.read_lexical_var, ","); }, /* * ```ebnf * lexical_var ::= '&'? T_VARIABLE * ``` */ read_lexical_var: function () { if (this.token === "&") { return this.read_byref(this.read_lexical_var.bind(this)); } const result = this.node("variable"); this.expect(this.tok.T_VARIABLE); const name = this.text().substring(1); this.next(); return result(name, false); }, /* * reads a list of parameters * ```ebnf * parameter_list ::= (parameter ',')* parameter? * ``` */ read_parameter_list: function () { const result = []; if (this.token != ")") { while (this.token != this.EOF) { result.push(this.read_parameter()); if (this.token == ",") { this.next(); } else if (this.token == ")") { break; } else { this.error([",", ")"]); break; } } } return result; }, /* * ```ebnf * parameter ::= type? '&'? T_ELLIPSIS? T_VARIABLE ('=' expr)? * ``` * @see https://github.com/php/php-src/blob/493524454d66adde84e00d249d607ecd540de99f/Zend/zend_language_parser.y#L640 */ read_parameter: function () { const node = this.node("parameter"); let parameterName = null; let value = null; let type = null; let nullable = false; if (this.token === "?") { this.next(); nullable = true; } type = this.read_type(); if (nullable && !type) { this.raiseError( "Expecting a type definition combined with nullable operator" ); } const isRef = this.is_reference(); const isVariadic = this.is_variadic(); if (this.expect(this.tok.T_VARIABLE)) { parameterName = this.node("identifier"); const name = this.text().substring(1); this.next(); parameterName = parameterName(name); } if (this.token == "=") { value = this.next().read_expr(); } return node(parameterName, type, value, isRef, isVariadic, nullable); }, /* * Reads a list of arguments * ```ebnf * function_argument_list ::= '(' (argument_list (',' argument_list)*)? ')' * ``` */ read_argument_list: function () { let result = []; this.expect("(") && this.next(); if (this.token !== ")") { result = this.read_non_empty_argument_list(); } this.expect(")") && this.next(); return result; }, /* * Reads non empty argument list */ read_non_empty_argument_list: function () { let wasVariadic = false; return this.read_function_list( function () { const argument = this.read_argument(); if (argument) { if (wasVariadic) { this.raiseError("Unexpected argument after a variadic argument"); } if (argument.kind === "variadic") { wasVariadic = true; } } return argument; }.bind(this), "," ); }, /* * ```ebnf * argument_list ::= T_ELLIPSIS? expr * ``` */ read_argument: function () { if (this.token === this.tok.T_ELLIPSIS) { return this.node("variadic")(this.next().read_expr()); } return this.read_expr(); }, /* * read type hinting * ```ebnf * type ::= T_ARRAY | T_CALLABLE | namespace_name * ``` */ read_type: function () { const result = this.node(); if (this.token === this.tok.T_ARRAY || this.token === this.tok.T_CALLABLE) { const type = this.text(); this.next(); return result("typereference", type.toLowerCase(), type); } else if (this.token === this.tok.T_STRING) { const type = this.text(); const backup = [this.token, this.lexer.getState()]; this.next(); if ( this.token !== this.tok.T_NS_SEPARATOR && this.ast.typereference.types.indexOf(type.toLowerCase()) > -1 ) { return result("typereference", type.toLowerCase(), type); } else { // rollback a classic namespace this.lexer.tokens.push(backup); this.next(); // fix : destroy not consumed node (release comments) result.destroy(); return this.read_namespace_name(); } } else if ( this.token === this.tok.T_NAMESPACE || this.token === this.tok.T_NS_SEPARATOR ) { // fix : destroy not consumed node (release comments) result.destroy(); return this.read_namespace_name(); } // fix : destroy not consumed node (release comments) result.destroy(); return null; }, };