UNPKG

php-parser

Version:

Parse PHP code from JS and returns its AST

240 lines (223 loc) 6.33 kB
/** * Copyright (C) 2020 Glayzzle (BSD3 License) * @authors https://github.com/glayzzle/php-parser/graphs/contributors * @url http://glayzzle.com */ "use strict"; const lexer = require("./lexer"); const parser = require("./parser"); const tokens = require("./tokens"); const AST = require("./ast"); /** * @private */ function combine(src, to) { const keys = Object.keys(src); let i = keys.length; while (i--) { const k = keys[i]; const val = src[k]; if (val === null) { delete to[k]; } else if (typeof val === "function") { to[k] = val.bind(to); } else if (Array.isArray(val)) { to[k] = Array.isArray(to[k]) ? to[k].concat(val) : val; } else if (typeof val === "object") { to[k] = typeof to[k] === "object" ? combine(val, to[k]) : val; } else { to[k] = val; } } return to; } /** * Initialise a new parser instance with the specified options * * @class * @memberOf module:php-parser * @tutorial Engine * @example * var parser = require('php-parser'); * var instance = new parser({ * parser: { * extractDoc: true, * suppressErrors: true, * version: 704 // or '7.4' * }, * ast: { * withPositions: true * }, * lexer: { * short_tags: true, * asp_tags: true * } * }); * * var evalAST = instance.parseEval('some php code'); * var codeAST = instance.parseCode('<?php some php code', 'foo.php'); * var tokens = instance.tokenGetAll('<?php some php code'); * * @param {Object} options - List of options * @property {Lexer} lexer * @property {Parser} parser * @property {AST} ast * @property {Object} tokens */ const Engine = function (options) { if (typeof this === "function") { return new this(options); } this.tokens = tokens; this.lexer = new lexer(this); this.ast = new AST(); this.parser = new parser(this.lexer, this.ast); if (options && typeof options === "object") { // disable php7 from lexer if already disabled from parser if (options.parser) { 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; } }; /** * Check if the inpyt is a buffer or a string * @private * @param {Buffer|String} buffer Input value that can be either a buffer or a string * @return {String} Returns the string from input */ const getStringBuffer = function (buffer) { return typeof buffer.write === "function" ? buffer.toString() : buffer; }; /** * Creates a new instance (Helper) * @param {Object} options * @return {Engine} * @private */ Engine.create = function (options) { return new Engine(options); }; /** * Evaluate the buffer * @private */ Engine.parseEval = function (buffer, options) { const self = new Engine(options); return self.parseEval(buffer); }; /** * Parse an evaluating mode string (no need to open php tags) * @param {String} buffer * @return {Program} */ Engine.prototype.parseEval = function (buffer) { this.lexer.mode_eval = true; this.lexer.all_tokens = false; buffer = getStringBuffer(buffer); return this.parser.parse(buffer, "eval"); }; /** * Static function that parse a php code with open/close tags * @private */ Engine.parseCode = function (buffer, filename, options) { if (typeof filename === "object" && !options) { // retro-compatibility options = filename; filename = "unknown"; } const self = new Engine(options); return self.parseCode(buffer, filename); }; /** * Function that parse a php code with open/close tags * * Sample code : * ```php * <?php $x = 1; * ``` * * Usage : * ```js * var parser = require('php-parser'); * var phpParser = new parser({ * // some options * }); * var ast = phpParser.parseCode('...php code...', 'foo.php'); * ``` * @param {String} buffer - The code to be parsed * @param {String} filename - Filename * @return {Program} */ Engine.prototype.parseCode = function (buffer, filename) { this.lexer.mode_eval = false; this.lexer.all_tokens = false; buffer = getStringBuffer(buffer); return this.parser.parse(buffer, filename); }; /** * Split the buffer into tokens * @private */ Engine.tokenGetAll = function (buffer, options) { const self = new Engine(options); return self.tokenGetAll(buffer); }; /** * Extract tokens from the specified buffer. * > Note that the output tokens are *STRICLY* similar to PHP function `token_get_all` * @param {string} buffer * @return {Array<string|string[]>} - Each item can be a string or an array with following informations [token_name, text, line_number] */ Engine.prototype.tokenGetAll = function (buffer) { this.lexer.mode_eval = false; this.lexer.all_tokens = true; buffer = getStringBuffer(buffer); const EOF = this.lexer.EOF; const names = this.tokens.values; this.lexer.setInput(buffer); let token = this.lexer.lex() || EOF; const result = []; while (token != EOF) { let entry = this.lexer.yytext; if (Object.prototype.hasOwnProperty.call(names, token)) { entry = [names[token], entry, this.lexer.yylloc.first_line]; } result.push(entry); token = this.lexer.lex() || EOF; } return result; }; /** @module php-parser */ // exports the function module.exports = Engine; // makes libraries public module.exports.tokens = tokens; module.exports.lexer = lexer; module.exports.AST = AST; module.exports.parser = parser; module.exports.combine = combine; module.exports.Engine = Engine; // allow the default export in index.d.ts module.exports.default = Engine;