UNPKG

php-parser

Version:

Parse PHP code from JS and returns its AST

542 lines (512 loc) 15.2 kB
/** * Copyright (C) 2018 Glayzzle (BSD3 License) * @authors https://github.com/glayzzle/php-parser/graphs/contributors * @url http://glayzzle.com */ "use strict"; module.exports = { /* * reading a class * ```ebnf * class ::= class_scope? T_CLASS T_STRING (T_EXTENDS NAMESPACE_NAME)? (T_IMPLEMENTS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' CLASS_BODY '}' * ``` */ read_class_declaration_statement: function () { const result = this.node("class"); const flag = this.read_class_modifiers(); // graceful mode : ignore token & go next if (this.token !== this.tok.T_CLASS) { this.error(this.tok.T_CLASS); this.next(); return null; } this.next().expect(this.tok.T_STRING); let propName = this.node("identifier"); const name = this.text(); this.next(); propName = propName(name); const propExtends = this.read_extends_from(); const propImplements = this.read_implements_list(); this.expect("{"); const body = this.next().read_class_body(); return result(propName, propExtends, propImplements, body, flag); }, read_class_modifiers: function () { return [0, 0, this.read_class_modifier()]; }, read_class_modifier: function () { const result = 0; if (this.token === this.tok.T_ABSTRACT) { this.next(); return 1; } else if (this.token === this.tok.T_FINAL) { this.next(); return 2; } return result; }, /* * Reads a class body * ```ebnf * class_body ::= (member_flags? (T_VAR | T_STRING | T_FUNCTION))* * ``` */ read_class_body: function () { let result = []; while (this.token !== this.EOF && this.token !== "}") { if (this.token === this.tok.T_COMMENT) { result.push(this.read_comment()); continue; } if (this.token === this.tok.T_DOC_COMMENT) { result.push(this.read_doc_comment()); continue; } // check T_USE trait if (this.token === this.tok.T_USE) { result = result.concat(this.read_trait_use_statement()); continue; } // read member flags const flags = this.read_member_flags(false); // check constant if (this.token === this.tok.T_CONST) { const constants = this.read_constant_list(flags); if (this.expect(";")) { this.next(); } result = result.concat(constants); continue; } // jump over T_VAR then land on T_VARIABLE if (this.token === this.tok.T_VAR) { this.next().expect(this.tok.T_VARIABLE); flags[0] = null; // public (as null) flags[1] = 0; // non static var } if (this.token === this.tok.T_FUNCTION) { // reads a function result.push(this.read_function(false, flags)); } else if ( this.token === this.tok.T_VARIABLE || // support https://wiki.php.net/rfc/typed_properties_v2 (this.version >= 704 && (this.token === "?" || this.token === this.tok.T_CALLABLE || this.token === this.tok.T_ARRAY || this.token === this.tok.T_NS_SEPARATOR || this.token === this.tok.T_STRING || this.token === this.tok.T_NAMESPACE)) ) { // reads a variable const variables = this.read_variable_list(flags); this.expect(";"); this.next(); result = result.concat(variables); } else { // raise an error this.error([ this.tok.T_CONST, this.tok.T_VARIABLE, this.tok.T_FUNCTION, ]); // ignore token this.next(); } } this.expect("}"); this.next(); return result; }, /* * Reads variable list * ```ebnf * variable_list ::= (variable_declaration ',')* variable_declaration * ``` */ read_variable_list: function (flags) { const result = this.node("propertystatement"); const properties = this.read_list( /* * Reads a variable declaration * * ```ebnf * variable_declaration ::= T_VARIABLE '=' scalar * ``` */ function read_variable_declaration() { const result = this.node("property"); const [nullable, type] = this.read_optional_type(); this.expect(this.tok.T_VARIABLE); let propName = this.node("identifier"); const name = this.text().substring(1); // ignore $ this.next(); propName = propName(name); if (this.token === ";" || this.token === ",") { return result(propName, null, nullable, type); } else if (this.token === "=") { // https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815 return result(propName, this.next().read_expr(), nullable, type); } else { this.expect([",", ";", "="]); return result(propName, null, nullable, type); } }, "," ); return result(null, properties, flags); }, /* * Reads constant list * ```ebnf * constant_list ::= T_CONST (constant_declaration ',')* constant_declaration * ``` */ read_constant_list: function (flags) { if (this.expect(this.tok.T_CONST)) { this.next(); } const result = this.node("classconstant"); const items = this.read_list( /* * Reads a constant declaration * * ```ebnf * constant_declaration ::= (T_STRING | IDENTIFIER) '=' expr * ``` * @return {Constant} [:link:](AST.md#constant) */ function read_constant_declaration() { const result = this.node("constant"); let constName = null; let value = null; if ( this.token === this.tok.T_STRING || (this.version >= 700 && this.is("IDENTIFIER")) ) { constName = this.node("identifier"); const name = this.text(); this.next(); constName = constName(name); } else { this.expect("IDENTIFIER"); } if (this.expect("=")) { value = this.next().read_expr(); } return result(constName, value); }, "," ); return result(null, items, flags); }, /* * Read member flags * @return array * 1st index : 0 => public, 1 => protected, 2 => private * 2nd index : 0 => instance member, 1 => static member * 3rd index : 0 => normal, 1 => abstract member, 2 => final member */ read_member_flags: function (asInterface) { const result = [-1, -1, -1]; if (this.is("T_MEMBER_FLAGS")) { let idx = 0, val = 0; do { switch (this.token) { case this.tok.T_PUBLIC: idx = 0; val = 0; break; case this.tok.T_PROTECTED: idx = 0; val = 1; break; case this.tok.T_PRIVATE: idx = 0; val = 2; break; case this.tok.T_STATIC: idx = 1; val = 1; break; case this.tok.T_ABSTRACT: idx = 2; val = 1; break; case this.tok.T_FINAL: idx = 2; val = 2; break; } if (asInterface) { if (idx == 0 && val == 2) { // an interface can't be private this.expect([this.tok.T_PUBLIC, this.tok.T_PROTECTED]); val = -1; } else if (idx == 2 && val == 1) { // an interface cant be abstract this.error(); val = -1; } } if (result[idx] !== -1) { // already defined flag this.error(); } else if (val !== -1) { result[idx] = val; } } while (this.next().is("T_MEMBER_FLAGS")); } if (result[1] == -1) result[1] = 0; if (result[2] == -1) result[2] = 0; return result; }, /* * optional_type: * /- empty -/ { $$ = NULL; } * | type_expr { $$ = $1; } * ; * * type_expr: * type { $$ = $1; } * | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } * | union_type { $$ = $1; } * ; * * type: * T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); } * | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); } * | name { $$ = $1; } * ; * * union_type: * type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } * | union_type '|' type { $$ = zend_ast_list_add($1, $3); } * ; */ read_optional_type: function () { let nullable = false; if (this.token === "?") { nullable = true; this.next(); } let type = this.read_type(); if (nullable && !type) { this.raiseError( "Expecting a type definition combined with nullable operator" ); } if (!nullable && !type) { return [false, null]; } if (this.token === "|") { type = [type]; do { this.next(); const variant = this.read_type(); if (!variant) { this.raiseError("Expecting a type definition"); break; } type.push(variant); } while (this.token === "|"); } return [nullable, type]; }, /* * reading an interface * ```ebnf * interface ::= T_INTERFACE T_STRING (T_EXTENDS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' INTERFACE_BODY '}' * ``` */ read_interface_declaration_statement: function () { const result = this.node("interface"); if (this.token !== this.tok.T_INTERFACE) { this.error(this.tok.T_INTERFACE); this.next(); return null; } this.next().expect(this.tok.T_STRING); let propName = this.node("identifier"); const name = this.text(); this.next(); propName = propName(name); const propExtends = this.read_interface_extends_list(); this.expect("{"); const body = this.next().read_interface_body(); return result(propName, propExtends, body); }, /* * Reads an interface body * ```ebnf * interface_body ::= (member_flags? (T_CONST | T_FUNCTION))* * ``` */ read_interface_body: function () { let result = []; while (this.token !== this.EOF && this.token !== "}") { if (this.token === this.tok.T_COMMENT) { result.push(this.read_comment()); continue; } if (this.token === this.tok.T_DOC_COMMENT) { result.push(this.read_doc_comment()); continue; } // read member flags const flags = this.read_member_flags(true); // check constant if (this.token == this.tok.T_CONST) { const constants = this.read_constant_list(flags); if (this.expect(";")) { this.next(); } result = result.concat(constants); } else if (this.token === this.tok.T_FUNCTION) { // reads a function const method = this.read_function_declaration(2, flags); method.parseFlags(flags); result.push(method); if (this.expect(";")) { this.next(); } } else { // raise an error this.error([this.tok.T_CONST, this.tok.T_FUNCTION]); this.next(); } } if (this.expect("}")) { this.next(); } return result; }, /* * reading a trait * ```ebnf * trait ::= T_TRAIT T_STRING (T_EXTENDS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' FUNCTION* '}' * ``` */ read_trait_declaration_statement: function () { const result = this.node("trait"); // graceful mode : ignore token & go next if (this.token !== this.tok.T_TRAIT) { this.error(this.tok.T_TRAIT); this.next(); return null; } this.next().expect(this.tok.T_STRING); let propName = this.node("identifier"); const name = this.text(); this.next(); propName = propName(name); this.expect("{"); const body = this.next().read_class_body(); return result(propName, body); }, /* * reading a use statement * ```ebnf * trait_use_statement ::= namespace_name (',' namespace_name)* ('{' trait_use_alias '}')? * ``` */ read_trait_use_statement: function () { // defines use statements const node = this.node("traituse"); this.expect(this.tok.T_USE) && this.next(); const traits = [this.read_namespace_name()]; let adaptations = null; while (this.token === ",") { traits.push(this.next().read_namespace_name()); } if (this.token === "{") { adaptations = []; // defines alias statements while (this.next().token !== this.EOF) { if (this.token === "}") break; adaptations.push(this.read_trait_use_alias()); this.expect(";"); } if (this.expect("}")) { this.next(); } } else { if (this.expect(";")) { this.next(); } } return node(traits, adaptations); }, /* * Reading trait alias * ```ebnf * trait_use_alias ::= namespace_name ( T_DOUBLE_COLON T_STRING )? (T_INSTEADOF namespace_name) | (T_AS member_flags? T_STRING) * ``` * name list : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L303 * trait adaptation : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L742 */ read_trait_use_alias: function () { const node = this.node(); let trait = null; let method; if (this.is("IDENTIFIER")) { method = this.node("identifier"); const methodName = this.text(); this.next(); method = method(methodName); } else { method = this.read_namespace_name(); if (this.token === this.tok.T_DOUBLE_COLON) { this.next(); if ( this.token === this.tok.T_STRING || (this.version >= 700 && this.is("IDENTIFIER")) ) { trait = method; method = this.node("identifier"); const methodName = this.text(); this.next(); method = method(methodName); } else { this.expect(this.tok.T_STRING); } } else { // convert identifier as string method = method.name; } } // handle trait precedence if (this.token === this.tok.T_INSTEADOF) { return node( "traitprecedence", trait, method, this.next().read_name_list() ); } else if (this.token === this.tok.T_AS) { // handle trait alias let flags = null; let alias = null; if (this.next().is("T_MEMBER_FLAGS")) { flags = this.read_member_flags(); } if ( this.token === this.tok.T_STRING || (this.version >= 700 && this.is("IDENTIFIER")) ) { alias = this.node("identifier"); const name = this.text(); this.next(); alias = alias(name); } else if (flags === false) { // no visibility flags and no name => too bad this.expect(this.tok.T_STRING); } return node("traitalias", trait, method, alias, flags); } // handle errors this.expect([this.tok.T_AS, this.tok.T_INSTEADOF]); return node("traitalias", trait, method, null, null); }, };